+Embedded libraries
+==================
+
+The source package embeds a copy of libgit2 0.25.1 because that is what this
+version of cargo needs, but the Debian libgit2 0.25.1 package cannot yet go
+into unstable due to the testing freeze. It will be removed after the freeze.
+
Updating the package
====================
+cargo (0.17.0-2) UNRELEASED; urgency=medium
+
+ * Re-embed libgit2 0.25.1 due to the Debian testing freeze. It will be
+ removed again after the freeze is over, when libgit2 0.25.1 can again
+ enter Debian unstable.
+
+ -- Ximin Luo <infinity0@debian.org> Wed, 03 May 2017 16:34:39 +0200
+
cargo (0.17.0-1) unstable; urgency=medium
* Upload to unstable so we have something to build rustc 1.17.0 with.
bash-completion,
libcurl4-gnutls-dev | libcurl4-openssl-dev,
libssh2-1-dev,
- libgit2-dev (>= 0.25.1),
+# TODO: re-enable after the freeze
+# libgit2-dev (>= 0.25.1),
libhttp-parser-dev,
libssl-dev,
zlib1g-dev,
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+
+# TODO: Everything below here can be removed after the freeze
+
+Files: debian/libgit2/*
+Copyright: 2009-2012, the libgit2 contributors
+License: GPL-2 with linking exception, origin admission
+
+Files: debian/libgit2/cmake/Modules/FindGSSAPI.cmake
+Copyright: 2013, Andreas Schneider <asn@cryptomilk.org>
+License: BSD-2-clause
+
+Files: debian/libgit2/include/git2/inttypes.h debian/libgit2/include/git2/stdint.h
+Copyright: 2006, Alexander Chemeris
+License: BSD-3-clause-modified
+
+Files: debian/libgit2/src/khash.h
+Copyright: 2008, 2009, 2011, Attractive Chaos <attractor@live.co.uk>
+License: MIT-License
+
+Files: debian/libgit2/src/tsort.c
+Copyright: 2010, Christopher Swenson
+ 2011, Vicent Marti
+License: MIT-License
+
+Files: debian/libgit2/src/path.c debian/libgit2/src/fnmatch.h
+Copyright: 2008, The Android Open Source Project
+License: BSD-2-clause
+
+Files: debian/libgit2/src/util.c
+Copyright: 2009, Public Software Group e. V., Berlin, Germany
+ 1990, Regents of the University of California.
+License: MIT-License and BSD-3-clause
+
+Files: debian/libgit2/src/xdiff/*
+Copyright: 2003, Davide Libenzi
+License: LGPL-2.1+
+
+Files: debian/libgit2/src/xdiff/xmerge.c
+Copyright: 2003-2006, Davide Libenzi
+ 2003-2006, Johannes E. Schindelin
+License: LGPL-2.1+
+
+Files: debian/libgit2/src/xdiff/xpatience.c
+Copyright: 2003-2009, Davide Libenzi
+ 2003-2009, Johannes E. Schindelin
+License: LGPL-2.1+
+
+Files: debian/libgit2/src/xdiff/xhistogram.c
+Copyright: 2010, Google Inc and others from JGit's IP log.
+License: EDL-1.0
+
+Files: debian/libgit2/src/win32/posix_w32.c
+Copyright: 1999 - 2012, The PHP Group
+License: PHP-3.01
+
+Files: debian/libgit2/src/fnmatch.c
+Copyright: 1989, 1993, 1994, The Regents of the University of California.
+License: BSD-3-clause
+
+Files: debian/libgit2/src/date.c
+Copyright: 2005, Linus Torvalds
+License: GPL-2 with linking exception
+ .
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+ .
+ LINKING EXCEPTION
+ .
+ In addition to the permissions in the GNU General Public License,
+ the authors give you unlimited permission to link the compiled
+ version of this library into combinations with other programs,
+ and to distribute those combinations without any restriction
+ coming from the use of this file. (The General Public License
+ restrictions do apply in other respects; for example, they cover
+ modification of the file, and distribution when not linked into
+ a combined executable.)
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+License: GPL-2 with linking exception, origin admission
+ libgit2 is Copyright (C) 2009-2011 the libgit2 contributors,
+ unless otherwise stated. See the AUTHORS file for details.
+ .
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+ .
+ LINKING EXCEPTION
+ .
+ In addition to the permissions in the GNU General Public License,
+ the authors give you unlimited permission to link the compiled
+ version of this library into combinations with other programs,
+ and to distribute those combinations without any restriction
+ coming from the use of this file. (The General Public License
+ restrictions do apply in other respects; for example, they cover
+ modification of the file, and distribution when not linked into
+ a combined executable.)
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+ .
+ ----------------------------------------------------------------------
+ .
+ The priority queue implementation is based on code licensed under the
+ Apache 2.0 license:
+ .
+ Copyright 2010 Volkan Yazıcı <volkan.yazici@gmail.com>
+ Copyright 2006-2010 The Apache Software Foundation
+ .
+ The full text of the Apache 2.0 license is available at:
+ .
+ http://www.apache.org/licenses/LICENSE-2.0
+
+License: BSD-3-clause
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. [rescinded 22 July 1999]
+ 4. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+License: BSD-3-clause-modified
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ .
+ 3. The name of the author may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+License: EDL-1.0
+ This program and the accompanying materials are made available
+ under the terms of the Eclipse Distribution License v1.0 which
+ accompanies this distribution, is reproduced below, and is
+ available at http://www.eclipse.org/org/documents/edl-v10.php
+ .
+ All rights reserved.
+ .
+ Redistribution and use in source and binary forms, with or
+ without modification, are permitted provided that the following
+ conditions are met:
+ .
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ .
+ - Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ .
+ - Neither the name of the Eclipse Foundation, Inc. nor the
+ names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: PHP-3.01
+ Redistribution and use in source and binary forms, with or without
+ modification, is permitted provided that the following conditions
+ are met:
+ .
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ .
+ 3. The name "PHP" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact group@php.net.
+ .
+ 4. Products derived from this software may not be called "PHP", nor
+ may "PHP" appear in their name, without prior written permission
+ from group@php.net. You may indicate that your software works in
+ conjunction with PHP by saying "Foo for PHP" instead of calling
+ it "PHP Foo" or "phpfoo"
+ .
+ 5. The PHP Group may publish revised and/or new versions of the
+ license from time to time. Each version will be given a
+ distinguishing version number.
+ Once covered code has been published under a particular version
+ of the license, you may always continue to use it under the terms
+ of that version. You may also choose to use such covered code
+ under the terms of any subsequent version of the license
+ published by the PHP Group. No one other than the PHP Group has
+ the right to modify the terms applicable to covered code created
+ under this License.
+ .
+ 6. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes PHP software, freely available from
+ <http://www.php.net/software/>".
+ .
+ THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
+ DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+/*
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2,
+ * as published by the Free Software Foundation.
+ *
+ * In addition to the permissions in the GNU General Public License,
+ * the authors give you unlimited permission to link the compiled
+ * version of this file into combinations with other programs,
+ * and to distribute those combinations without any restriction
+ * coming from the use of this file. (The General Public License
+ * restrictions do apply in other respects; for example, they cover
+ * modification of the file, and distribution when not linked into
+ * a combined executable.)
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
--- /dev/null
+; Check http://editorconfig.org/ for more informations
+; Top-most EditorConfig file
+root = true
+
+; tab indentation
+[*]
+indent_style = tab
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+; 4-column space indentation
+[*.md]
+indent_style = space
+indent_size = 4
--- /dev/null
+You are opening a _bug report_ against the libgit2 project. If you have a
+question about an API or usage, please ask on StackOverflow:
+http://stackoverflow.com/questions/tagged/libgit2. Please fill out the
+reproduction steps (below) and delete this introductory paragraph. Thanks!
+
+### Reproduction steps
+
+### Expected behavior
+
+### Actual behavior
+
+### Version of libgit2 (release number or SHA1)
+
+### Operating system(s) tested
--- /dev/null
+/tests/clar.suite
+/tests/clar.suite.rule
+/tests/.clarcache
+/apidocs
+/trash-*.exe
+/libgit2.pc
+/config.mak
+*.o
+*.a
+*.exe
+*.gcda
+*.gcno
+*.gcov
+.lock-wafbuild
+.waf*
+build/
+build-amiga/
+tests/tmp/
+msvc/Debug/
+msvc/Release/
+*.sln
+*.suo
+*.vc*proj*
+*.sdf
+*.opensdf
+*.aps
+*.cmake
+!cmake/Modules/*.cmake
+.DS_Store
+*~
+.*.swp
+tags
+mkmf.log
--- /dev/null
+Vicent Martà <vicent@github.com> Vicent Marti <tanoku@gmail.com>
+Vicent Martà <vicent@github.com> Vicent Martà <tanoku@gmail.com>
+Michael Schubert <schu@schu.io> schu <schu-github@schulog.org>
+Ben Straub <bs@github.com> Ben Straub <ben@straubnet.net>
+Ben Straub <bs@github.com> Ben Straub <bstraub@github.com>
+Carlos MartÃn Nieto <cmn@dwim.me> <carlos@cmartin.tk>
+Carlos MartÃn Nieto <cmn@dwim.me> <cmn@elego.de>
+nulltoken <emeric.fermas@gmail.com> <emeric.fermas@gmail.com>
+Scott J. Goldman <scottjg@github.com> <scottjgo@gmail.com>
+Martin Woodward <martin.woodward@microsoft.com> <martinwo@microsoft.com>
+Peter Drahoš <drahosp@gmail.com> <drahosp@gmail.com>
+Adam Roben <adam@github.com> <adam@roben.org>
+Adam Roben <adam@github.com> <adam@github.com>
+Xavier L. <xavier.l@afrosoft.tk> <xavier.l@afrosoft.ca>
+Xavier L. <xavier.l@afrosoft.tk> <xavier.l@afrosoft.tk>
+Sascha Cunz <sascha@babbelbox.org> <Sascha@BabbelBox.org>
+Authmillenon <authmillenon@googlemail.com> <martin@ucsmail.de>
+Authmillenon <authmillenon@googlemail.com> <authmillenon@googlemail.com>
+Edward Thomson <ethomson@github.com> <ethomson@microsoft.com>
+Edward Thomson <ethomson@github.com> <ethomson@edwardthomson.com>
+J. David Ibáñez <jdavid.ibp@gmail.com> <jdavid@itaapy.com>
+Russell Belfer <rb@github.com> <arrbee@arrbee.com>
--- /dev/null
+# Travis-CI Build for libgit2
+# see travis-ci.org for details
+
+language: c
+
+os:
+ - linux
+ - osx
+
+compiler:
+ - gcc
+ - clang
+
+# Settings to try
+env:
+ global:
+ - secure: "YnhS+8n6B+uoyaYfaJ3Lei7cSJqHDPiKJCKFIF2c87YDfmCvAJke8QtE7IzjYDs7UFkTCM4ox+ph2bERUrxZbSCyEkHdjIZpKuMJfYWja/jgMqTMxdyOH9y8JLFbZsSXDIXDwqBlC6vVyl1fP90M35wuWcNTs6tctfVWVofEFbs="
+ - GITTEST_INVASIVE_FS_SIZE=1
+ matrix:
+ - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release"
+ - OPTIONS="-DTHREADSAFE=OFF -DBUILD_EXAMPLES=ON"
+
+addons:
+ apt:
+ packages:
+ - cmake
+ - libssh2-1-dev
+ - openssh-client
+ - openssh-server
+ - valgrind
+
+sudo: false
+
+matrix:
+ fast_finish: true
+ exclude:
+ - os: osx
+ compiler: gcc
+ include:
+ - compiler: gcc
+ env: COVERITY=1
+ os: linux
+ - compiler: gcc
+ env:
+ - VALGRIND=1
+ OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug"
+ os: linux
+ allow_failures:
+ - env: COVERITY=1
+
+install:
+ - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi
+
+# Run the Build script and tests
+script:
+ - script/cibuild.sh
+
+# Run Tests
+after_success:
+ - if [ "$TRAVIS_OS_NAME" = "linux" -a -n "$VALGRIND" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi
+
+# Only watch the development and master branches
+branches:
+ only:
+ - master
+ - /^maint.*/
+
+# Notify development list when needed
+notifications:
+ irc:
+ channels:
+ - irc.freenode.net#libgit2
+ on_success: change
+ on_failure: always
+ use_notice: true
+ skip_join: true
+ campfire:
+ on_success: always
+ on_failure: always
+ rooms:
+ - secure: "sH0dpPWMirbEe7AvLddZ2yOp8rzHalGmv0bYL/LIhVw3JDI589HCYckeLMSB\n3e/FeXw4bn0EqXWEXijVa4ijbilVY6d8oprdqMdWHEodng4KvY5vID3iZSGT\nxylhahO1XHmRynKQLOAvxlc93IlpVW38vQfby8giIY1nkpspb2w="
--- /dev/null
+The following people contribute or have contributed
+to the libgit2 project (sorted alphabetically):
+
+Alex Budovski
+Alexei Sholik
+Andreas Ericsson
+Anton "antong" Gyllenberg
+Ankur Sethi
+Arthur Schreiber
+Ben Noordhuis
+Ben Straub
+Benjamin C Meyer
+Brian Downing
+Brian Lopez
+Carlos MartÃn Nieto
+Colin Timmermans
+Daniel Huckstep
+Dave Borowitz
+David Boyce
+David Glesser
+Dmitry Kakurin
+Dmitry Kovega
+Emeric Fermas
+Emmanuel Rodriguez
+Florian Forster
+Holger Weiss
+Ingmar Vanhassel
+J. David Ibáñez
+Jacques Germishuys
+Jakob Pfender
+Jason Penny
+Jason R. McNeil
+Jerome Lambourg
+Johan 't Hart
+John Wiegley
+Jonathan "Duke" Leto
+Julien Miotte
+Julio Espinoza-Sokal
+Justin Love
+Kelly "kelly.leahy" Leahy
+Kirill A. Shutemov
+Lambert CLARA
+Luc Bertrand
+Marc Pegon
+Marcel Groothuis
+Marco Villegas
+Michael "schu" Schubert
+Microsoft Corporation
+Olivier Ramonat
+Peter Drahoš
+Pierre Habouzit
+Pierre-Olivier Latour
+Przemyslaw Pawelczyk
+Ramsay Jones
+Robert G. Jakabosky
+Romain Geissler
+Romain Muller
+Russell Belfer
+Sakari Jokinen
+Samuel Charles "Sam" Day
+Sarath Lakshman
+Sascha Cunz
+Sascha Peilicke
+Scott Chacon
+Sebastian Schuberth
+Sergey Nikishin
+Shawn O. Pearce
+Shuhei Tanuma
+Steve Frécinaux
+Sven Strickroth
+Tim Branyen
+Tim Clem
+Tim Harder
+Torsten Bögershausen
+Trent Mick
+Vicent Marti
--- /dev/null
+v0.25 + 1
+-------
+
+### Changes or improvements
+
+### API additions
+
+### API removals
+
+### Breaking API changes
+
+v0.25
+-------
+
+### Changes or improvements
+
+* Fix repository discovery with `git_repository_discover` and
+ `git_repository_open_ext` to match git's handling of a ceiling
+ directory at the current directory. git only checks ceiling
+ directories when its search ascends to a parent directory. A ceiling
+ directory matching the starting directory will not prevent git from
+ finding a repository in the starting directory or a parent directory.
+
+* Do not fail when deleting remotes in the presence of broken
+ global configs which contain branches.
+
+* Support for reading and writing git index v4 files
+
+* Improve the performance of the revwalk and bring us closer to git's code.
+
+* The reference db has improved support for concurrency and returns `GIT_ELOCKED`
+ when an operation could not be performed due to locking.
+
+* Nanosecond resolution is now activated by default, following git's change to
+ do this.
+
+* We now restrict the set of ciphers we let OpenSSL use by default.
+
+* Users can now register their own merge drivers for use with `.gitattributes`.
+ The library also gained built-in support for the union merge driver.
+
+* The default for creating references is now to validate that the object does
+ exist.
+
+* Add `git_proxy_options` which is used by the different networking
+ implementations to let the caller specify the proxy settings instead of
+ relying on the environment variables.
+
+### API additions
+
+* You can now get the user-agent used by libgit2 using the
+ `GIT_OPT_GET_USER_AGENT` option with `git_libgit2_opts()`.
+ It is the counterpart to `GIT_OPT_SET_USER_AGENT`.
+
+* The `GIT_OPT_SET_SSL_CIPHERS` option for `git_libgit2_opts()` lets you specify
+ a custom list of ciphers to use for OpenSSL.
+
+* `git_commit_create_buffer()` creates a commit and writes it into a
+ user-provided buffer instead of writing it into the object db. Combine it with
+ `git_commit_create_with_signature()` in order to create a commit with a
+ cryptographic signature.
+
+* `git_blob_create_fromstream()` and
+ `git_blob_create_fromstream_commit()` allow you to create a blob by
+ writing into a stream. Useful when you do not know the final size or
+ want to copy the contents from another stream.
+
+* New flags for `git_repository_open_ext`:
+
+ * `GIT_REPOSITORY_OPEN_NO_DOTGIT` - Do not check for a repository by
+ appending `/.git` to the `start_path`; only open the repository if
+ `start_path` itself points to the git directory.
+ * `GIT_REPOSITORY_OPEN_FROM_ENV` - Find and open a git repository,
+ respecting the environment variables used by the git command-line
+ tools. If set, `git_repository_open_ext` will ignore the other
+ flags and the `ceiling_dirs` argument, and will allow a NULL
+ `path` to use `GIT_DIR` or search from the current directory. The
+ search for a repository will respect `$GIT_CEILING_DIRECTORIES`
+ and `$GIT_DISCOVERY_ACROSS_FILESYSTEM`. The opened repository
+ will respect `$GIT_INDEX_FILE`, `$GIT_NAMESPACE`,
+ `$GIT_OBJECT_DIRECTORY`, and `$GIT_ALTERNATE_OBJECT_DIRECTORIES`.
+ In the future, this flag will also cause `git_repository_open_ext`
+ to respect `$GIT_WORK_TREE` and `$GIT_COMMON_DIR`; currently,
+ `git_repository_open_ext` with this flag will error out if either
+ `$GIT_WORK_TREE` or `$GIT_COMMON_DIR` is set.
+
+* `git_diff_from_buffer()` can create a `git_diff` object from the contents
+ of a git-style patch file.
+
+* `git_index_version()` and `git_index_set_version()` to get and set
+ the index version
+
+* `git_odb_expand_ids()` lets you check for the existence of multiple
+ objects at once.
+
+* The new `git_blob_dup()`, `git_commit_dup()`, `git_tag_dup()` and
+ `git_tree_dup()` functions provide type-specific wrappers for
+ `git_object_dup()` to reduce noise and increase type safety for callers.
+
+* `git_reference_dup()` lets you duplicate a reference to aid in ownership
+ management and cleanup.
+
+* `git_signature_from_buffer()` lets you create a signature from a string in the
+ format that appear in objects.
+
+* `git_tree_create_updated()` lets you create a tree based on another one
+ together with a list of updates. For the covered update cases, it's more
+ efficient than the `git_index` route.
+
+* `git_apply_patch()` applies hunks from a `git_patch` to a buffer.
+
+* `git_diff_to_buf()` lets you print an entire diff directory to a buffer,
+ similar to how `git_patch_to_buf()` works.
+
+* `git_proxy_init_options()` is added to initialize a `git_proxy_options`
+ structure at run-time.
+
+* `git_merge_driver_register()`, `git_merge_driver_unregister()` let you
+ register and unregister a custom merge driver to be used when `.gitattributes`
+ specifies it.
+
+* `git_merge_driver_lookup()` can be used to look up a merge driver by name.
+
+* `git_merge_driver_source_repo()`, `git_merge_driver_source_ancestor()`,
+ `git_merge_driver_source_ours()`, `git_merge_driver_source_theirs()`,
+ `git_merge_driver_source_file_options()` added as accessors to
+ `git_merge_driver_source`.
+
+### API removals
+
+* `git_blob_create_fromchunks()` has been removed in favour of
+ `git_blob_create_fromstream()`.
+
+### Breaking API changes
+
+* `git_packbuilder_object_count` and `git_packbuilder_written` now
+ return a `size_t` instead of a `uint32_t` for more thorough
+ compatibility with the rest of the library.
+
+* `git_packbuiler_progress` now provides explicitly sized `uint32_t`
+ values instead of `unsigned int`.
+
+* `git_diff_file` now includes an `id_abbrev` field that reflects the
+ number of nibbles set in the `id` field.
+
+* `git_odb_backend` now has a `freshen` function pointer. This optional
+ function pointer is similar to the `exists` function, but it will update
+ a last-used marker. For filesystem-based object databases, this updates
+ the timestamp of the file containing the object, to indicate "freshness".
+ If this is `NULL`, then it will not be called and the `exists` function
+ will be used instead.
+
+* `git_remote_connect()` now accepts proxy options.
+
+v0.24
+-------
+
+### Changes or improvements
+
+* Custom merge drivers can now be registered, which allows callers to
+ configure callbacks to honor `merge=driver` configuration in
+ `.gitattributes`.
+
+* Custom filters can now be registered with wildcard attributes, for
+ example `filter=*`. Consumers should examine the attributes parameter
+ of the `check` function for details.
+
+* Symlinks are now followed when locking a file, which can be
+ necessary when multiple worktrees share a base repository.
+
+* You can now set your own user-agent to be sent for HTTP requests by
+ using the `GIT_OPT_SET_USER_AGENT` with `git_libgit2_opts()`.
+
+* You can set custom HTTP header fields to be sent along with requests
+ by passing them in the fetch and push options.
+
+* Tree objects are now assumed to be sorted. If a tree is not
+ correctly formed, it will give bad results. This is the git approach
+ and cuts a significant amount of time when reading the trees.
+
+* Filter registration is now protected against concurrent
+ registration.
+
+* Filenames which are not valid on Windows in an index no longer cause
+ to fail to parse it on that OS.
+
+* Rebases can now be performed purely in-memory, without touching the
+ repository's workdir.
+
+* When adding objects to the index, or when creating new tree or commit
+ objects, the inputs are validated to ensure that the dependent objects
+ exist and are of the correct type. This object validation can be
+ disabled with the GIT_OPT_ENABLE_STRICT_OBJECT_CREATION option.
+
+* The WinHTTP transport's handling of bad credentials now behaves like
+ the others, asking for credentials again.
+
+### API additions
+
+* `git_config_lock()` has been added, which allow for
+ transactional/atomic complex updates to the configuration, removing
+ the opportunity for concurrent operations and not committing any
+ changes until the unlock.
+
+* `git_diff_options` added a new callback `progress_cb` to report on the
+ progress of the diff as files are being compared. The documentation of
+ the existing callback `notify_cb` was updated to reflect that it only
+ gets called when new deltas are added to the diff.
+
+* `git_fetch_options` and `git_push_options` have gained a `custom_headers`
+ field to set the extra HTTP header fields to send.
+
+* `git_stream_register_tls()` lets you register a callback to be used
+ as the constructor for a TLS stream instead of the libgit2 built-in
+ one.
+
+* `git_commit_header_field()` allows you to look up a specific header
+ field in a commit.
+
+* `git_commit_extract_signature()` extracts the signature from a
+ commit and gives you both the signature and the signed data so you
+ can verify it.
+
+### API removals
+
+* No APIs were removed in this version.
+
+### Breaking API changes
+
+* `git_merge_options` now provides a `default_driver` that can be used
+ to provide the name of a merge driver to be used to handle files changed
+ during a merge.
+
+* The `git_merge_tree_flag_t` is now `git_merge_flag_t`. Subsequently,
+ its members are no longer prefixed with `GIT_MERGE_TREE_FLAG` but are
+ now prefixed with `GIT_MERGE_FLAG`, and the `tree_flags` field of the
+ `git_merge_options` structure is now named `flags`.
+
+* The `git_merge_file_flags_t` enum is now `git_merge_file_flag_t` for
+ consistency with other enum type names.
+
+* `git_cert` descendent types now have a proper `parent` member
+
+* It is the responsibility of the refdb backend to decide what to do
+ with the reflog on ref deletion. The file-based backend must delete
+ it, a database-backed one may wish to archive it.
+
+* `git_config_backend` has gained two entries. `lock` and `unlock`
+ with which to implement the transactional/atomic semantics for the
+ configuration backend.
+
+* `git_index_add` and `git_index_conflict_add()` will now use the case
+ as provided by the caller on case insensitive systems. Previous
+ versions would keep the case as it existed in the index. This does
+ not affect the higher-level `git_index_add_bypath` or
+ `git_index_add_frombuffer` functions.
+
+* The `notify_payload` field of `git_diff_options` was renamed to `payload`
+ to reflect that it's also the payload for the new progress callback.
+
+* The `git_config_level_t` enum has gained a higher-priority value
+ `GIT_CONFIG_LEVEL_PROGRAMDATA` which represent a rough Windows equivalent
+ to the system level configuration.
+
+* `git_rebase_options` now has a `merge_options` field.
+
+* The index no longer performs locking itself. This is not something
+ users of the library should have been relying on as it's not part of
+ the concurrency guarantees.
+
+* `git_remote_connect()` now takes a `custom_headers` argument to set
+ the extra HTTP header fields to send.
+
+v0.23
+------
+
+### Changes or improvements
+
+* Patience and minimal diff drivers can now be used for merges.
+
+* Merges can now ignore whitespace changes.
+
+* Updated binary identification in CRLF filtering to avoid false positives in
+ UTF-8 files.
+
+* Rename and copy detection is enabled for small files.
+
+* Checkout can now handle an initial checkout of a repository, making
+ `GIT_CHECKOUT_SAFE_CREATE` unnecessary for users of clone.
+
+* The signature parameter in the ref-modifying functions has been
+ removed. Use `git_repository_set_ident()` and
+ `git_repository_ident()` to override the signature to be used.
+
+* The local transport now auto-scales the number of threads to use
+ when creating the packfile instead of sticking to one.
+
+* Reference renaming now uses the right id for the old value.
+
+* The annotated version of branch creation, HEAD detaching and reset
+ allow for specifying the expression from the user to be put into the
+ reflog.
+
+* `git_rebase_commit` now returns `GIT_EUNMERGED` when you attempt to
+ commit with unstaged changes.
+
+* On Mac OS X, we now use SecureTransport to provide the cryptographic
+ support for HTTPS connections insead of OpenSSL.
+
+* Checkout can now accept an index for the baseline computations via the
+ `baseline_index` member.
+
+* The configuration for fetching is no longer stored inside the
+ `git_remote` struct but has been moved to a `git_fetch_options`. The
+ remote functions now take these options or the callbacks instead of
+ setting them beforehand.
+
+* `git_submodule` instances are no longer cached or shared across
+ lookup. Each submodule represents the configuration at the time of
+ loading.
+
+* The index now uses diffs for `add_all()` and `update_all()` which
+ gives it a speed boost and closer semantics to git.
+
+* The ssh transport now reports the stderr output from the server as
+ the error message, which allows you to get the "repository not
+ found" messages.
+
+* `git_index_conflict_add()` will remove staged entries that exist for
+ conflicted paths.
+
+* The flags for a `git_diff_file` will now have the `GIT_DIFF_FLAG_EXISTS`
+ bit set when a file exists on that side of the diff. This is useful
+ for understanding whether a side of the diff exists in the presence of
+ a conflict.
+
+* The constructor for a write-stream into the odb now takes
+ `git_off_t` instead of `size_t` for the size of the blob, which
+ allows putting large files into the odb on 32-bit systems.
+
+* The remote's push and pull URLs now honor the url.$URL.insteadOf
+ configuration. This allows modifying URL prefixes to a custom
+ value via gitconfig.
+
+* `git_diff_foreach`, `git_diff_blobs`, `git_diff_blob_to_buffer`,
+ and `git_diff_buffers` now accept a new binary callback of type
+ `git_diff_binary_cb` that includes the binary diff information.
+
+* The race condition mitigations described in `racy-git.txt` have been
+ implemented.
+
+* If libcurl is installed, we will use it to connect to HTTP(S)
+ servers.
+
+### API additions
+
+* The `git_merge_options` gained a `file_flags` member.
+
+* Parsing and retrieving a configuration value as a path is exposed
+ via `git_config_parse_path()` and `git_config_get_path()`
+ respectively.
+
+* `git_repository_set_ident()` and `git_repository_ident()` serve to
+ set and query which identity will be used when writing to the
+ reflog.
+
+* `git_config_entry_free()` frees a config entry.
+
+* `git_config_get_string_buf()` provides a way to safely retrieve a
+ string from a non-snapshot configuration.
+
+* `git_annotated_commit_from_revspec()` allows to get an annotated
+ commit from an extended sha synatx string.
+
+* `git_repository_set_head_detached_from_annotated()`,
+ `git_branch_create_from_annotated()` and
+ `git_reset_from_annotated()` allow for the caller to provide an
+ annotated commit through which they can control what expression is
+ put into the reflog as the source/target.
+
+* `git_index_add_frombuffer()` can now create a blob from memory
+ buffer and add it to the index which is attached to a repository.
+
+* The structure `git_fetch_options` has been added to determine the
+ runtime configuration for fetching, such as callbacks, pruning and
+ autotag behaviour. It has the runtime initializer
+ `git_fetch_init_options()`.
+
+* The enum `git_fetch_prune_t` has been added, letting you specify the
+ pruning behaviour for a fetch.
+
+* A push operation will notify the caller of what updates it indends
+ to perform on the remote, which provides similar information to
+ git's pre-push hook.
+
+* `git_stash_apply()` can now apply a stashed state from the stash list,
+ placing the data into the working directory and index.
+
+* `git_stash_pop()` will apply a stashed state (like `git_stash_apply()`)
+ but will remove the stashed state after a successful application.
+
+* A new error code `GIT_EEOF` indicates an early EOF from the
+ server. This typically indicates an error with the URL or
+ configuration of the server, and tools can use this to show messages
+ about failing to communicate with the server.
+
+* A new error code `GIT_EINVALID` indicates that an argument to a
+ function is invalid, or an invalid operation was requested.
+
+* `git_diff_index_to_workdir()` and `git_diff_tree_to_index()` will now
+ produce deltas of type `GIT_DELTA_CONFLICTED` to indicate that the index
+ side of the delta is a conflict.
+
+* The `git_status` family of functions will now produce status of type
+ `GIT_STATUS_CONFLICTED` to indicate that a conflict exists for that file
+ in the index.
+
+* `git_index_entry_is_conflict()` is a utility function to determine if
+ a given index entry has a non-zero stage entry, indicating that it is
+ one side of a conflict.
+
+* It is now possible to pass a keypair via a buffer instead of a
+ path. For this, `GIT_CREDTYPE_SSH_MEMORY` and
+ `git_cred_ssh_key_memory_new()` have been added.
+
+* `git_filter_list_contains` will indicate whether a particular
+ filter will be run in the given filter list.
+
+* `git_commit_header_field()` has been added, which allows retrieving
+ the contents of an arbitrary header field.
+
+* `git_submodule_set_branch()` allows to set the configured branch for
+ a submodule.
+
+### API removals
+
+* `git_remote_save()` and `git_remote_clear_refspecs()` have been
+ removed. Remote's configuration is changed via the configuration
+ directly or through a convenience function which performs changes to
+ the configuration directly.
+
+* `git_remote_set_callbacks()`, `git_remote_get_callbacks()` and
+ `git_remote_set_transport()` have been removed and the remote no
+ longer stores this configuration.
+
+* `git_remote_set_fetch_refpecs()` and
+ `git_remote_set_push_refspecs()` have been removed. There is no
+ longer a way to set the base refspecs at run-time.
+
+* `git_submodule_save()` has been removed. The submodules are no
+ longer configured via the objects.
+
+* `git_submodule_reload_all()` has been removed as we no longer cache
+ submodules.
+
+### Breaking API changes
+
+* `git_smart_subtransport_cb` now has a `param` parameter.
+
+* The `git_merge_options` structure member `flags` has been renamed
+ to `tree_flags`.
+
+* The `git_merge_file_options` structure member `flags` is now
+ an unsigned int. It was previously a `git_merge_file_flags_t`.
+
+* `GIT_CHECKOUT_SAFE_CREATE` has been removed. Most users will generally
+ be able to switch to `GIT_CHECKOUT_SAFE`, but if you require missing
+ file handling during checkout, you may now use `GIT_CHECKOUT_SAFE |
+ GIT_CHECKOUT_RECREATE_MISSING`.
+
+* The `git_clone_options` and `git_submodule_update_options`
+ structures no longer have a `signature` field.
+
+* The following functions have removed the signature and/or log message
+ parameters in favour of git-emulating ones.
+
+ * `git_branch_create()`, `git_branch_move()`
+ * `git_rebase_init()`, `git_rebase_abort()`
+ * `git_reference_symbolic_create_matching()`,
+ `git_reference_symbolic_create()`, `git_reference_create()`,
+ `git_reference_create_matching()`,
+ `git_reference_symbolic_set_target()`,
+ `git_reference_set_target()`, `git_reference_rename()`
+ * `git_remote_update_tips()`, `git_remote_fetch()`, `git_remote_push()`
+ * `git_repository_set_head()`,
+ `git_repository_set_head_detached()`,
+ `git_repository_detach_head()`
+ * `git_reset()`
+
+* `git_config_get_entry()` now gives back a ref-counted
+ `git_config_entry`. You must free it when you no longer need it.
+
+* `git_config_get_string()` will return an error if used on a
+ non-snapshot configuration, as there can be no guarantee that the
+ returned pointer is valid.
+
+* `git_note_default_ref()` now uses a `git_buf` to return the string,
+ as the string is otherwise not guaranteed to stay allocated.
+
+* `git_rebase_operation_current()` will return `GIT_REBASE_NO_OPERATION`
+ if it is called immediately after creating a rebase session but before
+ you have applied the first patch.
+
+* `git_rebase_options` now contains a `git_checkout_options` struct
+ that will be used for functions that modify the working directory,
+ namely `git_rebase_init`, `git_rebase_next` and
+ `git_rebase_abort`. As a result, `git_rebase_open` now also takes
+ a `git_rebase_options` and only the `git_rebase_init` and
+ `git_rebase_open` functions take a `git_rebase_options`, where they
+ will persist the options to subsequent `git_rebase` calls.
+
+* The `git_clone_options` struct now has fetch options in a
+ `fetch_opts` field instead of remote callbacks in
+ `remote_callbacks`.
+
+* The remote callbacks has gained a new member `push_negotiation`
+ which gets called before sending the update commands to the server.
+
+* The following functions no longer act on a remote instance but
+ change the repository's configuration. Their signatures have changed
+ accordingly:
+
+ * `git_remote_set_url()`, `git_remote_seturl()`
+ * `git_remote_add_fetch()`, `git_remote_add_push()` and
+ * `git_remote_set_autotag()`
+
+* `git_remote_connect()` and `git_remote_prune()` now take a pointer
+ to the callbacks.
+
+* `git_remote_fetch()` and `git_remote_download()` now take a pointer
+ to fetch options which determine the runtime configuration.
+
+* The `git_remote_autotag_option_t` values have been changed. It has
+ gained a `_UNSPECIFIED` default value to specify no override for the
+ configured setting.
+
+* `git_remote_update_tips()` now takes a pointer to the callbacks as
+ well as a boolean whether to write `FETCH_HEAD` and the autotag
+ setting.
+
+* `git_remote_create_anonymous()` no longer takes a fetch refspec as
+ url-only remotes cannot have configured refspecs.
+
+* The `git_submodule_update_options` struct now has fetch options in
+ the `fetch_opts` field instead of callbacks in the
+ `remote_callbacks` field.
+
+* The following functions no longer act on a submodule instance but
+ change the repository's configuration. Their signatures have changed
+ accordingly:
+
+ * `git_submodule_set_url()`, `git_submodule_set_ignore()`,
+ `git_submodule_set_update()`,
+ `git_submodule_set_fetch_recurse_submodules()`.
+
+* `git_submodule_status()` no longer takes a submodule instance but a
+ repsitory, a submodule name and an ignore setting.
+
+* The `push` function in the `git_transport` interface now takes a
+ pointer to the remote callbacks.
+
+* The `git_index_entry` struct's fields' types have been changed to
+ more accurately reflect what is in fact stored in the
+ index. Specifically, time and file size are 32 bits intead of 64, as
+ these values are truncated.
+
+* `GIT_EMERGECONFLICT` is now `GIT_ECONFLICT`, which more accurately
+ describes the nature of the error.
+
+* It is no longer allowed to call `git_buf_grow()` on buffers
+ borrowing the memory they point to.
+
+v0.22
+------
+
+### Changes or improvements
+
+* `git_signature_new()` now requires a non-empty email address.
+
+* Use CommonCrypto libraries for SHA-1 calculation on Mac OS X.
+
+* Disable SSL compression and SSLv2 and SSLv3 ciphers in favor of TLSv1
+ in OpenSSL.
+
+* The fetch behavior of remotes with autotag set to `GIT_REMOTE_DOWNLOAD_TAGS_ALL`
+ has been changed to match git 1.9.0 and later. In this mode, libgit2 now
+ fetches all tags in addition to whatever else needs to be fetched.
+
+* `git_checkout()` now handles case-changing renames correctly on
+ case-insensitive filesystems; for example renaming "readme" to "README".
+
+* The search for libssh2 is now done via pkg-config instead of a
+ custom search of a few directories.
+
+* Add support for core.protectHFS and core.protectNTFS. Add more
+ validation for filenames which we write such as references.
+
+* The local transport now generates textual progress output like
+ git-upload-pack does ("counting objects").
+
+* `git_checkout_index()` can now check out an in-memory index that is not
+ necessarily the repository's index, so you may check out an index
+ that was produced by git_merge and friends while retaining the cached
+ information.
+
+* Remove the default timeout for receiving / sending data over HTTP using
+ the WinHTTP transport layer.
+
+* Add SPNEGO (Kerberos) authentication using GSSAPI on Unix systems.
+
+* Provide built-in objects for the empty blob (e69de29) and empty
+ tree (4b825dc) objects.
+
+* The index' tree cache is now filled upon read-tree and write-tree
+ and the cache is written to disk.
+
+* LF -> CRLF filter refuses to handle mixed-EOL files
+
+* LF -> CRLF filter now runs when * text = auto (with Git for Windows 1.9.4)
+
+* File unlocks are atomic again via rename. Read-only files on Windows are
+ made read-write if necessary.
+
+* Share open packfiles across repositories to share descriptors and mmaps.
+
+* Use a map for the treebuilder, making insertion O(1)
+
+* The build system now accepts an option EMBED_SSH_PATH which when set
+ tells it to include a copy of libssh2 at the given location. This is
+ enabled for MSVC.
+
+* Add support for refspecs with the asterisk in the middle of a
+ pattern.
+
+* Fetching now performs opportunistic updates. To achieve this, we
+ introduce a difference between active and passive refspecs, which
+ make `git_remote_download()` and `git_remote_fetch()` to take a list of
+ resfpecs to be the active list, similarly to how git fetch accepts a
+ list on the command-line.
+
+* The THREADSAFE option to build libgit2 with threading support has
+ been flipped to be on by default.
+
+* The remote object has learnt to prune remote-tracking branches. If
+ the remote is configured to do so, this will happen via
+ `git_remote_fetch()`. You can also call `git_remote_prune()` after
+ connecting or fetching to perform the prune.
+
+
+### API additions
+
+* Introduce `git_buf_text_is_binary()` and `git_buf_text_contains_nul()` for
+ consumers to perform binary detection on a git_buf.
+
+* `git_branch_upstream_remote()` has been introduced to provide the
+ branch.<name>.remote configuration value.
+
+* Introduce `git_describe_commit()` and `git_describe_workdir()` to provide
+ a description of the current commit (and working tree, respectively)
+ based on the nearest tag or reference
+
+* Introduce `git_merge_bases()` and the `git_oidarray` type to expose all
+ merge bases between two commits.
+
+* Introduce `git_merge_bases_many()` to expose all merge bases between
+ multiple commits.
+
+* Introduce rebase functionality (using the merge algorithm only).
+ Introduce `git_rebase_init()` to begin a new rebase session,
+ `git_rebase_open()` to open an in-progress rebase session,
+ `git_rebase_commit()` to commit the current rebase operation,
+ `git_rebase_next()` to apply the next rebase operation,
+ `git_rebase_abort()` to abort an in-progress rebase and `git_rebase_finish()`
+ to complete a rebase operation.
+
+* Introduce `git_note_author()` and `git_note_committer()` to get the author
+ and committer information on a `git_note`, respectively.
+
+* A factory function for ssh has been added which allows to change the
+ path of the programs to execute for receive-pack and upload-pack on
+ the server, `git_transport_ssh_with_paths()`.
+
+* The ssh transport supports asking the remote host for accepted
+ credential types as well as multiple challeges using a single
+ connection. This requires to know which username you want to connect
+ as, so this introduces the USERNAME credential type which the ssh
+ transport will use to ask for the username.
+
+* The `GIT_EPEEL` error code has been introduced when we cannot peel a tag
+ to the requested object type; if the given object otherwise cannot be
+ peeled, `GIT_EINVALIDSPEC` is returned.
+
+* Introduce `GIT_REPOSITORY_INIT_RELATIVE_GITLINK` to use relative paths
+ when writing gitlinks, as is used by git core for submodules.
+
+* `git_remote_prune()` has been added. See above for description.
+
+
+* Introduce reference transactions, which allow multiple references to
+ be locked at the same time and updates be queued. This also allows
+ us to safely update a reflog with arbitrary contents, as we need to
+ do for stash.
+
+### API removals
+
+* `git_remote_supported_url()` and `git_remote_is_valid_url()` have been
+ removed as they have become essentially useless with rsync-style ssh paths.
+
+* `git_clone_into()` and `git_clone_local_into()` have been removed from the
+ public API in favour of `git_clone callbacks`.
+
+* The option to ignore certificate errors via `git_remote_cert_check()`
+ is no longer present. Instead, `git_remote_callbacks` has gained a new
+ entry which lets the user perform their own certificate checks.
+
+### Breaking API changes
+
+* `git_cherry_pick()` is now `git_cherrypick()`.
+
+* The `git_submodule_update()` function was renamed to
+ `git_submodule_update_strategy()`. `git_submodule_update()` is now used to
+ provide functionalty similar to "git submodule update".
+
+* `git_treebuilder_create()` was renamed to `git_treebuilder_new()` to better
+ reflect it being a constructor rather than something which writes to
+ disk.
+
+* `git_treebuilder_new()` (was `git_treebuilder_create()`) now takes a
+ repository so that it can query repository configuration.
+ Subsequently, `git_treebuilder_write()` no longer takes a repository.
+
+* `git_threads_init()` and `git_threads_shutdown()` have been renamed to
+ `git_libgit2_init()` and `git_libgit2_shutdown()` to better explain what
+ their purpose is, as it's grown to be more than just about threads.
+
+* `git_libgit2_init()` and `git_libgit2_shutdown()` now return the number of
+ initializations of the library, so consumers may schedule work on the
+ first initialization.
+
+* The `git_transport_register()` function no longer takes a priority and takes
+ a URL scheme name (eg "http") instead of a prefix like "http://"
+
+* `git_index_name_entrycount()` and `git_index_reuc_entrycount()` now
+ return size_t instead of unsigned int.
+
+* The `context_lines` and `interhunk_lines` fields in `git_diff`_options are
+ now `uint32_t` instead of `uint16_t`. This allows to set them to `UINT_MAX`,
+ in effect asking for "infinite" context e.g. to iterate over all the
+ unmodified lines of a diff.
+
+* `git_status_file()` now takes an exact path. Use `git_status_list_new()` if
+ pathspec searching is needed.
+
+* `git_note_create()` has changed the position of the notes reference
+ name to match `git_note_remove()`.
+
+* Rename `git_remote_load()` to `git_remote_lookup()` to bring it in line
+ with the rest of the lookup functions.
+
+* `git_remote_rename()` now takes the repository and the remote's
+ current name. Accepting a remote indicates we want to change it,
+ which we only did partially. It is much clearer if we accept a name
+ and no loaded objects are changed.
+
+* `git_remote_delete()` now accepts the repository and the remote's name
+ instead of a loaded remote.
+
+* `git_merge_head` is now `git_annotated_commit`, to better reflect its usage
+ for multiple functions (including rebase)
+
+* The `git_clone_options` struct no longer provides the `ignore_cert_errors` or
+ `remote_name` members for remote customization.
+
+ Instead, the `git_clone_options` struct has two new members, `remote_cb` and
+ `remote_cb_payload`, which allow the caller to completely override the remote
+ creation process. If needed, the caller can use this callback to give their
+ remote a name other than the default (origin) or disable cert checking.
+
+ The `remote_callbacks` member has been preserved for convenience, although it
+ is not used when a remote creation callback is supplied.
+
+* The `git_clone`_options struct now provides `repository_cb` and
+ `repository_cb_payload` to allow the user to create a repository with
+ custom options.
+
+* The `git_push` struct to perform a push has been replaced with
+ `git_remote_upload()`. The refspecs and options are passed as a
+ function argument. `git_push_update_tips()` is now also
+ `git_remote_update_tips()` and the callbacks are in the same struct as
+ the rest.
+
+* The `git_remote_set_transport()` function now sets a transport factory function,
+ rather than a pre-existing transport instance.
+
+* The `git_transport` structure definition has moved into the sys/transport.h
+ file.
+
+* libgit2 no longer automatically sets the OpenSSL locking
+ functions. This is not something which we can know to do. A
+ last-resort convenience function is provided in sys/openssl.h,
+ `git_openssl_set_locking()` which can be used to set the locking.
--- /dev/null
+# CMake build script for the libgit2 project
+#
+# Building (out of source build):
+# > mkdir build && cd build
+# > cmake .. [-DSETTINGS=VALUE]
+# > cmake --build .
+#
+# Testing:
+# > ctest -V
+#
+# Install:
+# > cmake --build . --target install
+
+PROJECT(libgit2 C)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+CMAKE_POLICY(SET CMP0015 NEW)
+
+# Add find modules to the path
+SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+
+INCLUDE(CheckLibraryExists)
+INCLUDE(CheckFunctionExists)
+INCLUDE(CheckSymbolExists)
+INCLUDE(CheckStructHasMember)
+INCLUDE(AddCFlagIfSupported)
+INCLUDE(FindPkgConfig)
+
+# Build options
+#
+OPTION( SONAME "Set the (SO)VERSION of the target" ON )
+OPTION( BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON )
+OPTION( THREADSAFE "Build libgit2 as threadsafe" ON )
+OPTION( BUILD_CLAR "Build Tests using the Clar suite" ON )
+OPTION( BUILD_EXAMPLES "Build library usage example apps" OFF )
+OPTION( TAGS "Generate tags" OFF )
+OPTION( PROFILE "Generate profiling information" OFF )
+OPTION( ENABLE_TRACE "Enables tracing support" OFF )
+OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF )
+
+OPTION( USE_ICONV "Link with and use iconv library" OFF )
+OPTION( USE_SSH "Link with libssh to enable SSH support" ON )
+OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF )
+OPTION( VALGRIND "Configure build for valgrind" OFF )
+OPTION( CURL "Use curl for HTTP if available" ON)
+OPTION( DEBUG_POOL "Enable debug pool allocator" OFF )
+
+IF(DEBUG_POOL)
+ ADD_DEFINITIONS(-DGIT_DEBUG_POOL)
+ENDIF()
+
+IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ SET( USE_ICONV ON )
+ FIND_PACKAGE(Security)
+ FIND_PACKAGE(CoreFoundation REQUIRED)
+ENDIF()
+
+IF(MSVC)
+ # This option is only available when building with MSVC. By default, libgit2
+ # is build using the cdecl calling convention, which is useful if you're
+ # writing C. However, the CLR and Win32 API both expect stdcall.
+ #
+ # If you are writing a CLR program and want to link to libgit2, you'll want
+ # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument.
+ OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF )
+
+ # This option must match the settings used in your program, in particular if you
+ # are linking statically
+ OPTION( STATIC_CRT "Link the static CRT libraries" ON )
+
+ # If you want to embed a copy of libssh2 into libgit2, pass a
+ # path to libssh2
+ OPTION( EMBED_SSH_PATH "Path to libssh2 to embed (Windows)" OFF )
+
+ ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS)
+ ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE)
+ ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE)
+ENDIF()
+
+
+IF(WIN32)
+ # By default, libgit2 is built with WinHTTP. To use the built-in
+ # HTTP transport, invoke CMake with the "-DWINHTTP=OFF" argument.
+ OPTION( WINHTTP "Use Win32 WinHTTP routines" ON )
+ENDIF()
+
+IF(MSVC)
+ # Enable MSVC CRTDBG memory leak reporting when in debug mode.
+ OPTION(MSVC_CRTDBG "Enable CRTDBG memory leak reporting" OFF)
+ENDIF()
+
+IF (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ OPTION( USE_OPENSSL "Link with and use openssl library" ON )
+ENDIF()
+
+CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtim "sys/types.h;sys/stat.h"
+ HAVE_STRUCT_STAT_ST_MTIM LANGUAGE C)
+CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtimespec "sys/types.h;sys/stat.h"
+ HAVE_STRUCT_STAT_ST_MTIMESPEC LANGUAGE C)
+CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtime_nsec sys/stat.h
+ HAVE_STRUCT_STAT_MTIME_NSEC LANGUAGE C)
+
+IF (HAVE_STRUCT_STAT_ST_MTIM)
+ CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtim.tv_nsec sys/stat.h
+ HAVE_STRUCT_STAT_NSEC LANGUAGE C)
+ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC)
+ CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtimespec.tv_nsec sys/stat.h
+ HAVE_STRUCT_STAT_NSEC LANGUAGE C)
+ELSE ()
+ SET( HAVE_STRUCT_STAT_NSEC ON )
+ENDIF()
+
+IF (HAVE_STRUCT_STAT_NSEC OR WIN32)
+ OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" ON )
+ENDIF()
+
+# This variable will contain the libraries we need to put into
+# libgit2.pc's Requires.private. That is, what we're linking to or
+# what someone who's statically linking us needs to link to.
+SET(LIBGIT2_PC_REQUIRES "")
+# This will be set later if we use the system's http-parser library or
+# use iconv (OSX) and will be written to the Libs.private field in the
+# pc file.
+SET(LIBGIT2_PC_LIBS "")
+
+# Installation paths
+#
+SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.")
+SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.")
+SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.")
+
+# Set a couple variables to be substituted inside the .pc file.
+# We can't just use LIB_INSTALL_DIR in the .pc file, as passing them as absolue
+# or relative paths is both valid and supported by cmake.
+SET (PKGCONFIG_PREFIX ${CMAKE_INSTALL_PREFIX})
+
+IF(IS_ABSOLUTE ${LIB_INSTALL_DIR})
+ SET (PKGCONFIG_LIBDIR ${LIB_INSTALL_DIR})
+ELSE(IS_ABSOLUTE ${LIB_INSTALL_DIR})
+ SET (PKGCONFIG_LIBDIR "\${prefix}/${LIB_INSTALL_DIR}")
+ENDIF (IS_ABSOLUTE ${LIB_INSTALL_DIR})
+
+IF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR})
+ SET (PKGCONFIG_INCLUDEDIR ${INCLUDE_INSTALL_DIR})
+ELSE(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR})
+ SET (PKGCONFIG_INCLUDEDIR "\${prefix}/${INCLUDE_INSTALL_DIR}")
+ENDIF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR})
+
+FUNCTION(TARGET_OS_LIBRARIES target)
+ IF(WIN32)
+ TARGET_LINK_LIBRARIES(${target} ws2_32)
+ ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ TARGET_LINK_LIBRARIES(${target} socket nsl)
+ LIST(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl")
+ SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE)
+ ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Haiku")
+ TARGET_LINK_LIBRARIES(${target} network)
+ LIST(APPEND LIBGIT2_PC_LIBS "-lnetwork")
+ SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE)
+ ENDIF()
+ CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" NEED_LIBRT)
+ IF(NEED_LIBRT)
+ TARGET_LINK_LIBRARIES(${target} rt)
+ LIST(APPEND LIBGIT2_PC_LIBS "-lrt")
+ SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE)
+ ENDIF()
+
+ IF(THREADSAFE)
+ TARGET_LINK_LIBRARIES(${target} ${CMAKE_THREAD_LIBS_INIT})
+ LIST(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT})
+ SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE)
+ ENDIF()
+ENDFUNCTION()
+
+# This function splits the sources files up into their appropriate
+# subdirectories. This is especially useful for IDEs like Xcode and
+# Visual Studio, so that you can navigate into the libgit2_clar project,
+# and see the folders within the tests folder (instead of just seeing all
+# source and tests in a single folder.)
+FUNCTION(IDE_SPLIT_SOURCES target)
+ IF(MSVC_IDE OR CMAKE_GENERATOR STREQUAL Xcode)
+ GET_TARGET_PROPERTY(sources ${target} SOURCES)
+ FOREACH(source ${sources})
+ IF(source MATCHES ".*/")
+ STRING(REPLACE ${CMAKE_CURRENT_SOURCE_DIR}/ "" rel ${source})
+ IF(rel)
+ STRING(REGEX REPLACE "/([^/]*)$" "" rel ${rel})
+ IF(rel)
+ STRING(REPLACE "/" "\\\\" rel ${rel})
+ SOURCE_GROUP(${rel} FILES ${source})
+ ENDIF()
+ ENDIF()
+ ENDIF()
+ ENDFOREACH()
+ ENDIF()
+ENDFUNCTION()
+
+FILE(STRINGS "include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$")
+
+STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${GIT2_HEADER}")
+STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_MINOR "${GIT2_HEADER}")
+STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_REV "${GIT2_HEADER}")
+SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}")
+
+FILE(STRINGS "include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$")
+STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}")
+
+# Find required dependencies
+INCLUDE_DIRECTORIES(src include)
+
+IF (SECURITY_FOUND)
+ # OS X 10.7 and older do not have some functions we use, fall back to OpenSSL there
+ CHECK_LIBRARY_EXISTS("${SECURITY_DIRS}" SSLCreateContext "Security/SecureTransport.h" HAVE_NEWER_SECURITY)
+ IF (HAVE_NEWER_SECURITY)
+ MESSAGE("-- Found Security ${SECURITY_DIRS}")
+ LIST(APPEND LIBGIT2_PC_LIBS "-framework Security")
+ ELSE()
+ MESSAGE("-- Security framework is too old, falling back to OpenSSL")
+ SET(SECURITY_FOUND "NO")
+ SET(SECURITY_DIRS "")
+ SET(SECURITY_DIR "")
+ SET(USE_OPENSSL "ON")
+ ENDIF()
+ENDIF()
+
+IF (COREFOUNDATION_FOUND)
+ MESSAGE("-- Found CoreFoundation ${COREFOUNDATION_DIRS}")
+ LIST(APPEND LIBGIT2_PC_LIBS "-framework CoreFoundation")
+ENDIF()
+
+
+IF (WIN32 AND EMBED_SSH_PATH)
+ FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c")
+ INCLUDE_DIRECTORIES("${EMBED_SSH_PATH}/include")
+ FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"")
+ ADD_DEFINITIONS(-DGIT_SSH)
+ENDIF()
+
+IF (WIN32 AND WINHTTP)
+ ADD_DEFINITIONS(-DGIT_WINHTTP)
+ INCLUDE_DIRECTORIES(deps/http-parser)
+ FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
+
+ # Since MinGW does not come with headers or an import library for winhttp,
+ # we have to include a private header and generate our own import library
+ IF (MINGW)
+ FIND_PROGRAM(DLLTOOL dlltool CMAKE_FIND_ROOT_PATH_BOTH)
+ IF (NOT DLLTOOL)
+ MESSAGE(FATAL_ERROR "Could not find dlltool command")
+ ENDIF ()
+
+ SET(LIBWINHTTP_PATH "${CMAKE_CURRENT_BINARY_DIR}/deps/winhttp")
+ FILE(MAKE_DIRECTORY ${LIBWINHTTP_PATH})
+
+ IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(WINHTTP_DEF "${CMAKE_CURRENT_SOURCE_DIR}/deps/winhttp/winhttp64.def")
+ ELSE()
+ set(WINHTTP_DEF "${CMAKE_CURRENT_SOURCE_DIR}/deps/winhttp/winhttp.def")
+ ENDIF()
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT ${LIBWINHTTP_PATH}/libwinhttp.a
+ COMMAND ${DLLTOOL} -d ${WINHTTP_DEF} -k -D winhttp.dll -l libwinhttp.a
+ DEPENDS ${WINHTTP_DEF}
+ WORKING_DIRECTORY ${LIBWINHTTP_PATH}
+ )
+
+ SET_SOURCE_FILES_PROPERTIES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/transports/winhttp.c
+ PROPERTIES OBJECT_DEPENDS ${LIBWINHTTP_PATH}/libwinhttp.a
+ )
+
+ INCLUDE_DIRECTORIES(deps/winhttp)
+ LINK_DIRECTORIES(${LIBWINHTTP_PATH})
+ ENDIF ()
+
+ LINK_LIBRARIES(winhttp rpcrt4 crypt32 ole32)
+ LIST(APPEND LIBGIT2_PC_LIBS "-lwinhttp" "-lrpcrt4" "-lcrypt32" "-lole32")
+ELSE ()
+ IF (CURL)
+ PKG_CHECK_MODULES(CURL libcurl)
+ ENDIF ()
+
+ IF (NOT AMIGA AND USE_OPENSSL)
+ FIND_PACKAGE(OpenSSL)
+ ENDIF ()
+
+ IF (CURL_FOUND)
+ ADD_DEFINITIONS(-DGIT_CURL)
+ INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${CURL_LIBRARY_DIRS})
+ LINK_LIBRARIES(${CURL_LIBRARIES})
+ LIST(APPEND LIBGIT2_PC_LIBS ${CURL_LDFLAGS})
+ ENDIF()
+
+ FIND_PACKAGE(HTTP_Parser)
+ IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2)
+ INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS})
+ LINK_LIBRARIES(${HTTP_PARSER_LIBRARIES})
+ LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser")
+ ELSE()
+ MESSAGE(STATUS "http-parser was not found or is too old; using bundled 3rd-party sources.")
+ INCLUDE_DIRECTORIES(deps/http-parser)
+ FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
+ ENDIF()
+ENDIF()
+
+# Specify sha1 implementation
+IF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin")
+ ADD_DEFINITIONS(-DWIN32_SHA1)
+ FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
+ELSEIF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ ADD_DEFINITIONS(-DGIT_COMMON_CRYPTO)
+ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin")
+ ADD_DEFINITIONS(-DOPENSSL_SHA1)
+ IF (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ LIST(APPEND LIBGIT2_PC_LIBS "-lssl")
+ ELSE()
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} openssl")
+ ENDIF ()
+ELSE()
+ FILE(GLOB SRC_SHA1 src/hash/hash_generic.c)
+ENDIF()
+
+# Enable tracing
+IF (ENABLE_TRACE STREQUAL "ON")
+ ADD_DEFINITIONS(-DGIT_TRACE)
+ENDIF()
+
+# Include POSIX regex when it is required
+IF(WIN32 OR AMIGA OR CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ INCLUDE_DIRECTORIES(deps/regex)
+ SET(SRC_REGEX deps/regex/regex.c)
+ENDIF()
+
+# Optional external dependency: zlib
+FIND_PACKAGE(ZLIB)
+IF (ZLIB_FOUND)
+ INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS})
+ LINK_LIBRARIES(${ZLIB_LIBRARIES})
+ IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ LIST(APPEND LIBGIT2_PC_LIBS "-lz")
+ ELSE()
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} zlib")
+ ENDIF()
+ELSE()
+ MESSAGE(STATUS "zlib was not found; using bundled 3rd-party sources." )
+ INCLUDE_DIRECTORIES(deps/zlib)
+ ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP)
+ FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h)
+ENDIF()
+
+# Optional external dependency: libssh2
+IF (USE_SSH)
+ PKG_CHECK_MODULES(LIBSSH2 libssh2)
+ENDIF()
+IF (LIBSSH2_FOUND)
+ ADD_DEFINITIONS(-DGIT_SSH)
+ INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${LIBSSH2_LIBRARY_DIRS})
+ LIST(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS})
+ #SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} ${LIBSSH2_LDFLAGS}")
+ SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
+
+ CHECK_LIBRARY_EXISTS("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS)
+ IF (HAVE_LIBSSH2_MEMORY_CREDENTIALS)
+ ADD_DEFINITIONS(-DGIT_SSH_MEMORY_CREDENTIALS)
+ ENDIF()
+ELSE()
+ MESSAGE(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.")
+ENDIF()
+
+# Optional external dependency: libgssapi
+IF (USE_GSSAPI)
+ FIND_PACKAGE(GSSAPI)
+ENDIF()
+IF (GSSAPI_FOUND)
+ ADD_DEFINITIONS(-DGIT_GSSAPI)
+ENDIF()
+
+# Optional external dependency: iconv
+IF (USE_ICONV)
+ FIND_PACKAGE(Iconv)
+ENDIF()
+IF (ICONV_FOUND)
+ ADD_DEFINITIONS(-DGIT_USE_ICONV)
+ INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR})
+ LIST(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES})
+ENDIF()
+
+# Platform specific compilation flags
+IF (MSVC)
+
+ STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+
+ # /GF - String pooling
+ # /MP - Parallel build
+ SET(CMAKE_C_FLAGS "/GF /MP /nologo ${CMAKE_C_FLAGS}")
+
+ IF (STDCALL)
+ # /Gz - stdcall calling convention
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz")
+ ENDIF ()
+
+ IF (STATIC_CRT)
+ SET(CRT_FLAG_DEBUG "/MTd")
+ SET(CRT_FLAG_RELEASE "/MT")
+ ELSE()
+ SET(CRT_FLAG_DEBUG "/MDd")
+ SET(CRT_FLAG_RELEASE "/MD")
+ ENDIF()
+
+ IF (MSVC_CRTDBG)
+ SET(CRT_FLAG_DEBUG "${CRT_FLAG_DEBUG} /DGIT_MSVC_CRTDBG")
+ SET(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES}" "Dbghelp.lib")
+ ENDIF()
+
+ # /Zi - Create debugging information
+ # /Od - Disable optimization
+ # /D_DEBUG - #define _DEBUG
+ # /MTd - Statically link the multithreaded debug version of the CRT
+ # /MDd - Dynamically link the multithreaded debug version of the CRT
+ # /RTC1 - Run time checks
+ SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Zi /Od /D_DEBUG /RTC1 ${CRT_FLAG_DEBUG}")
+
+ # /DNDEBUG - Disables asserts
+ # /MT - Statically link the multithreaded release version of the CRT
+ # /MD - Dynamically link the multithreaded release version of the CRT
+ # /O2 - Optimize for speed
+ # /Oy - Enable frame pointer omission (FPO) (otherwise CMake will automatically turn it off)
+ # /GL - Link time code generation (whole program optimization)
+ # /Gy - Function-level linking
+ SET(CMAKE_C_FLAGS_RELEASE "/DNDEBUG /O2 /Oy /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /Oy- - Disable frame pointer omission (FPO)
+ SET(CMAKE_C_FLAGS_RELWITHDEBINFO "/DNDEBUG /Zi /O2 /Oy- /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /O1 - Optimize for size
+ SET(CMAKE_C_FLAGS_MINSIZEREL "/DNDEBUG /O1 /Oy /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /DYNAMICBASE - Address space load randomization (ASLR)
+ # /NXCOMPAT - Data execution prevention (DEP)
+ # /LARGEADDRESSAWARE - >2GB user address space on x86
+ # /VERSION - Embed version information in PE header
+ SET(CMAKE_EXE_LINKER_FLAGS "/DYNAMICBASE /NXCOMPAT /LARGEADDRESSAWARE /VERSION:${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}")
+
+ # /DEBUG - Create a PDB
+ # /LTCG - Link time code generation (whole program optimization)
+ # /OPT:REF /OPT:ICF - Fold out duplicate code at link step
+ # /INCREMENTAL:NO - Required to use /LTCG
+ # /DEBUGTYPE:cv,fixup - Additional data embedded in the PDB (requires /INCREMENTAL:NO, so not on for Debug)
+ SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG")
+ SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
+ SET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO /DEBUGTYPE:cv,fixup")
+ SET(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
+
+ # Same linker settings for DLL as EXE
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}")
+
+ SET(WIN_RC "src/win32/git2.rc")
+
+ # Precompiled headers
+
+ELSE ()
+ SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra ${CMAKE_C_FLAGS}")
+
+ IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ SET(CMAKE_C_FLAGS "-std=c99 -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__ -D_POSIX_PTHREAD_SEMANTICS ${CMAKE_C_FLAGS}")
+ ENDIF()
+
+ IF (WIN32 AND NOT CYGWIN)
+ SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")
+ ENDIF ()
+
+ IF (MINGW OR MSYS) # MinGW and MSYS always do PIC and complain if we tell them to
+ STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}")
+ ELSEIF (BUILD_SHARED_LIBS)
+ ADD_C_FLAG_IF_SUPPORTED(-fvisibility=hidden)
+
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
+ ENDIF ()
+
+ IF (MINGW)
+ # MinGW >= 3.14 uses the C99-style stdio functions
+ # automatically, but forks like mingw-w64 still want
+ # us to define this in order to use them
+ ADD_DEFINITIONS(-D__USE_MINGW_ANSI_STDIO=1)
+ ENDIF ()
+
+ ADD_C_FLAG_IF_SUPPORTED(-Wdocumentation)
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-missing-field-initializers)
+ ADD_C_FLAG_IF_SUPPORTED(-Wstrict-aliasing=2)
+ ADD_C_FLAG_IF_SUPPORTED(-Wstrict-prototypes)
+ ADD_C_FLAG_IF_SUPPORTED(-Wdeclaration-after-statement)
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-const-variable)
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-function)
+
+ IF (APPLE) # Apple deprecated OpenSSL
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-deprecated-declarations)
+ ENDIF()
+
+ IF (PROFILE)
+ SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}")
+ SET(CMAKE_EXE_LINKER_FLAGS "-pg ${CMAKE_EXE_LINKER_FLAGS}")
+ ENDIF ()
+ENDIF()
+
+CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L)
+IF (HAVE_REGCOMP_L)
+ ADD_DEFINITIONS(-DHAVE_REGCOMP_L)
+ENDIF ()
+
+CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS)
+IF (HAVE_FUTIMENS)
+ ADD_DEFINITIONS(-DHAVE_FUTIMENS)
+ENDIF ()
+
+CHECK_FUNCTION_EXISTS(qsort_r HAVE_QSORT_R)
+IF (HAVE_QSORT_R)
+ ADD_DEFINITIONS(-DHAVE_QSORT_R)
+ENDIF ()
+
+CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S)
+IF (HAVE_QSORT_S)
+ ADD_DEFINITIONS(-DHAVE_QSORT_S)
+ENDIF ()
+
+IF( NOT CMAKE_CONFIGURATION_TYPES )
+ # Build Debug by default
+ IF (NOT CMAKE_BUILD_TYPE)
+ SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+ ENDIF ()
+ELSE()
+ # Using a multi-configuration generator eg MSVC or Xcode
+ # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE
+ENDIF()
+
+IF (SECURITY_FOUND)
+ ADD_DEFINITIONS(-DGIT_SECURE_TRANSPORT)
+ INCLUDE_DIRECTORIES(${SECURITY_INCLUDE_DIR})
+ENDIF ()
+
+IF (OPENSSL_FOUND)
+ ADD_DEFINITIONS(-DGIT_OPENSSL)
+ INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
+ SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES})
+ENDIF()
+
+
+
+IF (THREADSAFE)
+ IF (NOT WIN32)
+ FIND_PACKAGE(Threads REQUIRED)
+ ENDIF()
+
+ ADD_DEFINITIONS(-DGIT_THREADS)
+ENDIF()
+
+IF (USE_NSEC)
+ ADD_DEFINITIONS(-DGIT_USE_NSEC)
+ENDIF()
+
+IF (HAVE_STRUCT_STAT_ST_MTIM)
+ ADD_DEFINITIONS(-DGIT_USE_STAT_MTIM)
+ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC)
+ ADD_DEFINITIONS(-DGIT_USE_STAT_MTIMESPEC)
+ELSEIF (HAVE_STRUCT_STAT_ST_MTIME_NSEC)
+ ADD_DEFINITIONS(-DGIT_USE_STAT_MTIME_NSEC)
+ENDIF()
+
+ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
+
+# Collect sourcefiles
+FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h)
+
+# On Windows use specific platform sources
+IF (WIN32 AND NOT CYGWIN)
+ ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501)
+ FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h)
+ELSEIF (AMIGA)
+ ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP)
+ELSE()
+ IF (VALGRIND)
+ ADD_DEFINITIONS(-DNO_MMAP)
+ ENDIF()
+ FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h)
+ENDIF()
+FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h)
+
+# Determine architecture of the machine
+IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ ADD_DEFINITIONS(-DGIT_ARCH_64)
+ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4)
+ ADD_DEFINITIONS(-DGIT_ARCH_32)
+ELSEIF (CMAKE_SIZEOF_VOID_P)
+ MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)")
+ELSE()
+ MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)")
+ENDIF()
+
+# Compile and link libgit2
+ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1} ${WIN_RC})
+TARGET_LINK_LIBRARIES(git2 ${SECURITY_DIRS})
+TARGET_LINK_LIBRARIES(git2 ${COREFOUNDATION_DIRS})
+TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${GSSAPI_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${ICONV_LIBRARIES})
+TARGET_OS_LIBRARIES(git2)
+
+# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240)
+# Win64+MSVC+static libs = linker error
+IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS)
+ SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64")
+ENDIF()
+
+IDE_SPLIT_SOURCES(git2)
+
+IF (SONAME)
+ SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
+ SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION})
+ IF (LIBGIT2_FILENAME)
+ ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\")
+ SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME})
+ ELSEIF (DEFINED LIBGIT2_PREFIX)
+ SET_TARGET_PROPERTIES(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}")
+ ENDIF()
+ENDIF()
+STRING(REPLACE ";" " " LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS}")
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
+
+IF (MSVC_IDE)
+ # Precompiled headers
+ SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
+ SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h")
+ENDIF ()
+
+# Install
+INSTALL(TARGETS git2
+ RUNTIME DESTINATION ${BIN_INSTALL_DIR}
+ LIBRARY DESTINATION ${LIB_INSTALL_DIR}
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
+)
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig )
+INSTALL(DIRECTORY include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} )
+INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} )
+
+# Tests
+IF (BUILD_CLAR)
+ FIND_PACKAGE(PythonInterp)
+
+ IF(NOT PYTHONINTERP_FOUND)
+ MESSAGE(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. "
+ "Make sure python is available, or pass -DBUILD_CLAR=OFF to skip building the tests")
+ ENDIF()
+
+ SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/")
+ SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests")
+ SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.")
+ ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\")
+ ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\")
+ ADD_DEFINITIONS(-DCLAR_TMPDIR=\"libgit2_tests\")
+
+ INCLUDE_DIRECTORIES(${CLAR_PATH})
+ FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h)
+ SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar_libgit2_trace.c" "${CLAR_PATH}/clar_libgit2_timer.c" "${CLAR_PATH}/clar.c")
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT ${CLAR_PATH}/clar.suite
+ COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress .
+ DEPENDS ${SRC_TEST}
+ WORKING_DIRECTORY ${CLAR_PATH}
+ )
+
+ SET_SOURCE_FILES_PROPERTIES(
+ ${CLAR_PATH}/clar.c
+ PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite)
+
+ ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1})
+
+ TARGET_LINK_LIBRARIES(libgit2_clar ${COREFOUNDATION_DIRS})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${SECURITY_DIRS})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${GSSAPI_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${ICONV_LIBRARIES})
+ TARGET_OS_LIBRARIES(libgit2_clar)
+ IDE_SPLIT_SOURCES(libgit2_clar)
+
+ IF (MSVC_IDE)
+ # Precompiled headers
+ SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
+ ENDIF ()
+
+ ENABLE_TESTING()
+ IF (WINHTTP OR OPENSSL_FOUND OR SECURITY_FOUND)
+ ADD_TEST(libgit2_clar libgit2_clar -ionline)
+ ELSE ()
+ ADD_TEST(libgit2_clar libgit2_clar -v)
+ ENDIF ()
+
+ # Add a test target which runs the cred callback tests, to be
+ # called after setting the url and user
+ ADD_TEST(libgit2_clar-cred_callback libgit2_clar -v -sonline::clone::cred_callback)
+ ADD_TEST(libgit2_clar-proxy_credentials_in_url libgit2_clar -v -sonline::clone::proxy_credentials_in_url)
+ ADD_TEST(libgit2_clar-proxy_credentials_request libgit2_clar -v -sonline::clone::proxy_credentials_request)
+ENDIF ()
+
+IF (TAGS)
+ FIND_PROGRAM(CTAGS ctags)
+ IF (NOT CTAGS)
+ MESSAGE(FATAL_ERROR "Could not find ctags command")
+ ENDIF ()
+
+ FILE(GLOB_RECURSE SRC_ALL *.[ch])
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT tags
+ COMMAND ${CTAGS} -a ${SRC_ALL}
+ DEPENDS ${SRC_ALL}
+ )
+ ADD_CUSTOM_TARGET(
+ do_tags ALL
+ DEPENDS tags
+ )
+ENDIF ()
+
+IF (BUILD_EXAMPLES)
+ ADD_SUBDIRECTORY(examples)
+ENDIF ()
--- /dev/null
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [libgit2@gmail.com][email]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[email]: mailto:libgit2@gmail.com
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
--- /dev/null
+# Welcome to libgit2!
+
+We're making it easy to do interesting things with git, and we'd love to have
+your help.
+
+## Licensing
+
+By contributing to libgit2, you agree to release your contribution under
+the terms of the license. Except for the `examples` directory, all code
+is released under the [GPL v2 with linking exception](COPYING).
+
+The `examples` code is governed by the
+[CC0 Public Domain Dedication](examples/COPYING), so that you may copy
+from them into your own application.
+
+## Discussion & Chat
+
+We hang out in the
+[`#libgit2`](http://webchat.freenode.net/?channels=#libgit2)) channel on
+irc.freenode.net.
+
+Also, feel free to open an
+[Issue](https://github.com/libgit2/libgit2/issues/new) to start a discussion
+about any concerns you have. We like to use Issues for that so there is an
+easily accessible permanent record of the conversation.
+
+## Libgit2 Versions
+
+The `master` branch is the main branch where development happens.
+Releases are tagged
+(e.g. [v0.21.0](https://github.com/libgit2/libgit2/releases/tag/v0.21.0) )
+and when a critical bug fix needs to be backported, it will be done on a
+`<tag>-maint` maintenance branch.
+
+## Reporting Bugs
+
+First, know which version of libgit2 your problem is in and include it in
+your bug report. This can either be a tag (e.g.
+[v0.17.0](https://github.com/libgit2/libgit2/releases/tag/v0.17.0)) or a
+commit SHA
+(e.g. [01be7863](https://github.com/libgit2/libgit2/commit/01be7863)).
+Using [`git describe`](http://git-scm.com/docs/git-describe) is a
+great way to tell us what version you're working with.
+
+If you're not running against the latest `master` branch version,
+please compile and test against that to avoid re-reporting an issue that's
+already been fixed.
+
+It's *incredibly* helpful to be able to reproduce the problem. Please
+include a list of steps, a bit of code, and/or a zipped repository (if
+possible). Note that some of the libgit2 developers are employees of
+GitHub, so if your repository is private, find us on IRC and we'll figure
+out a way to help you.
+
+## Pull Requests
+
+Our work flow is a [typical GitHub
+flow](https://guides.github.com/introduction/flow/index.html), where
+contributors fork the [libgit2 repository](https://github.com/libgit2/libgit2),
+make their changes on branch, and submit a
+[Pull Request](https://help.github.com/articles/using-pull-requests)
+(a.k.a. "PR"). Pull requests should usually be targeted at the `master`
+branch.
+
+Life will be a lot easier for you (and us) if you follow this pattern
+(i.e. fork, named branch, submit PR). If you use your fork's `master`
+branch directly, things can get messy.
+
+Please include a nice description of your changes when you submit your PR;
+if we have to read the whole diff to figure out why you're contributing
+in the first place, you're less likely to get feedback and have your change
+merged in.
+
+If you are starting to work on a particular area, feel free to submit a PR
+that highlights your work in progress (and note in the PR title that it's
+not ready to merge). These early PRs are welcome and will help in getting
+visibility for your fix, allow others to comment early on the changes and
+also let others know that you are currently working on something.
+
+Before wrapping up a PR, you should be sure to:
+
+* Write tests to cover any functional changes
+* Update documentation for any changed public APIs
+* Add to the [`CHANGELOG.md`](CHANGELOG.md) file describing any major changes
+
+## Unit Tests
+
+We believe that our unit tests allow us to keep the quality of libgit2
+high: any new changes must not cause unit test failures, and new changes
+should include unit tests that cover the bug fixes or new features.
+For bug fixes, we prefer unit tests that illustrate the failure before
+the change, but pass with your changes.
+
+In addition to new tests, please ensure that your changes do not cause
+any other test failures. Running the entire test suite is helpful
+before you submit a pull request. When you build libgit2, the test
+suite will also be built. You can run most of the tests by simply running
+the resultant `libgit2_clar` binary. If you want to run a specific
+unit test, you can name it with the `-s` option. For example:
+
+ libgit2_clar -sstatus::worktree::long_filenames
+
+Or you can run an entire class of tests. For example, to run all the
+worktree status tests:
+
+ libgit2_clar -sstatus::worktree
+
+The default test run is fairly exhaustive, but it will exclude some
+unit tests by default: in particular, those that talk to network
+servers and the tests that manipulate the filesystem in onerous
+ways (and may need to have special privileges to run). To run the
+network tests:
+
+ libgit2_clar -ionline
+
+In addition, various tests may be enabled by environment variables,
+like the ones that write exceptionally large repositories or manipulate
+the filesystem structure in unexpected ways. These tests *may be
+dangerous* to run on a normal machine and may harm your filesystem. It's
+not recommended that you run these; instead, the continuous integration
+servers will run these (in a sandbox).
+
+## Porting Code From Other Open-Source Projects
+
+`libgit2` is licensed under the terms of the GPL v2 with a linking
+exception. Any code brought in must be compatible with those terms.
+
+The most common case is porting code from core Git. Git is a pure GPL
+project, which means that in order to port code to this project, we need the
+explicit permission of the author. Check the
+[`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors)
+file for authors who have already consented.
+
+Other licenses have other requirements; check the license of the library
+you're porting code *from* to see what you need to do. As a general rule,
+MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0
+license typically doesn't work due to GPL incompatibility.
+
+If your pull request uses code from core Git, another project, or code
+from a forum / Stack Overflow, then *please* flag this in your PR and make
+sure you've given proper credit to the original author in the code
+snippet.
+
+## Style Guide
+
+The public API of `libgit2` is [ANSI C](http://en.wikipedia.org/wiki/ANSI_C)
+(a.k.a. C89) compatible. Internally, `libgit2` is written using a portable
+subset of C99 - in order to compile with GCC, Clang, MSVC, etc., we keep
+local variable declarations at the tops of blocks only and avoid `//` style
+comments. Additionally, `libgit2` follows some extra conventions for
+function and type naming, code formatting, and testing.
+
+We like to keep the source code consistent and easy to read. Maintaining
+this takes some discipline, but it's been more than worth it. Take a look
+at the [conventions
+file](https://github.com/libgit2/libgit2/blob/development/CONVENTIONS.md).
+
+## Starter Projects
+
+See our [projects
+list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
--- /dev/null
+# Libgit2 Conventions
+
+We like to keep the source consistent and readable. Herein are some
+guidelines that should help with that.
+
+## External API
+
+We have a few rules to avoid surprising ways of calling functions and
+some rules for consumers of the library to avoid stepping on each
+other's toes.
+
+ - Property accessors return the value directly (e.g. an `int` or
+ `const char *`) but if a function can fail, we return a `int` value
+ and the output parameters go first in the parameter list, followed
+ by the object that a function is operating on, and then any other
+ arguments the function may need.
+
+ - If a function returns an object as a return value, that function is
+ a getter and the object's lifetime is tied to the parent
+ object. Objects which are returned as the first argument as a
+ pointer-to-pointer are owned by the caller and it is repsponsible
+ for freeing it. Strings are returned via `git_buf` in order to
+ allow for re-use and safe freeing.
+
+ - Most of what libgit2 does relates to I/O so you as a general rule
+ you should assume that any function can fail due to errors as even
+ getting data from the filesystem can result in all sorts of errors
+ and complex failure cases.
+
+ - Paths inside the Git system are separated by a slash (0x2F). If a
+ function accepts a path on disk, then backslashes (0x5C) are also
+ accepted on Windows.
+
+ - Do not mix allocators. If something has been allocated by libgit2,
+ you do not know which is the right free function in the general
+ case. Use the free functions provided for each object type.
+
+## Compatibility
+
+`libgit2` runs on many different platforms with many different compilers.
+
+The public API of `libgit2` is [ANSI C](http://en.wikipedia.org/wiki/ANSI_C)
+(a.k.a. C89) compatible.
+
+Internally, `libgit2` is written using a portable subset of C99 - in order
+to maximize compatibility (e.g. with MSVC) we avoid certain C99
+extensions. Specifically, we keep local variable declarations at the tops
+of blocks only and we avoid `//` style comments.
+
+Also, to the greatest extent possible, we try to avoid lots of `#ifdef`s
+inside the core code base. This is somewhat unavoidable, but since it can
+really hamper maintainability, we keep it to a minimum.
+
+## Match Surrounding Code
+
+If there is one rule to take away from this document, it is *new code should
+match the surrounding code in a way that makes it impossible to distinguish
+the new from the old.* Consistency is more important to us than anyone's
+personal opinion about where braces should be placed or spaces vs. tabs.
+
+If a section of code is being completely rewritten, it is okay to bring it
+in line with the standards that are laid out here, but we will not accept
+submissions that contain a large number of changes that are merely
+reformatting.
+
+## Naming Things
+
+All external types and functions start with `git_` and all `#define` macros
+start with `GIT_`. The `libgit2` API is mostly broken into related
+functional modules each with a corresponding header. All functions in a
+module should be named like `git_modulename_functioname()`
+(e.g. `git_repository_open()`).
+
+Functions with a single output parameter should name that parameter `out`.
+Multiple outputs should be named `foo_out`, `bar_out`, etc.
+
+Parameters of type `git_oid` should be named `id`, or `foo_id`. Calls that
+return an OID should be named `git_foo_id`.
+
+Where a callback function is used, the function should also include a
+user-supplied extra input that is a `void *` named "payload" that will be
+passed through to the callback at each invocation.
+
+## Typedefs
+
+Wherever possible, use `typedef`. In some cases, if a structure is just a
+collection of function pointers, the pointer types don't need to be
+separately typedef'd, but loose function pointer types should be.
+
+## Exports
+
+All exported functions must be declared as:
+
+```c
+GIT_EXTERN(result_type) git_modulename_functionname(arg_list);
+```
+
+## Internals
+
+Functions whose *modulename* is followed by two underscores,
+for example `git_odb__read_packed`, are semi-private functions.
+They are primarily intended for use within the library itself,
+and may disappear or change their signature in a future release.
+
+## Parameters
+
+Out parameters come first.
+
+Whenever possible, pass argument pointers as `const`. Some structures (such
+as `git_repository` and `git_index`) have mutable internal structure that
+prevents this.
+
+Callbacks should always take a `void *` payload as their last parameter.
+Callback pointers are grouped with their payloads, and typically come last
+when passed as arguments:
+
+```c
+int git_foo(git_repository *repo, git_foo_cb callback, void *payload);
+```
+
+## Memory Ownership
+
+Some APIs allocate memory which the caller is responsible for freeing; others
+return a pointer into a buffer that's owned by some other object. Make this
+explicit in the documentation.
+
+## Return codes
+
+Most public APIs should return an `int` error code. As is typical with most
+C library functions, a zero value indicates success and a negative value
+indicates failure.
+
+Some bindings will transform these returned error codes into exception
+types, so returning a semantically appropriate error code is important.
+Check
+[`include/git2/errors.h`](https://github.com/libgit2/libgit2/blob/development/include/git2/errors.h)
+for the return codes already defined.
+
+In your implementation, use `giterr_set()` to provide extended error
+information to callers.
+
+If a `libgit2` function internally invokes another function that reports an
+error, but the error is not propagated up, use `giterr_clear()` to prevent
+callers from getting the wrong error message later on.
+
+
+## Structs
+
+Most public types should be opaque, e.g.:
+
+```C
+typedef struct git_odb git_odb;
+```
+
+...with allocation functions returning an "instance" created within
+the library, and not within the application. This allows the type
+to grow (or shrink) in size without rebuilding client code.
+
+To preserve ABI compatibility, include an `int version` field in all opaque
+structures, and initialize to the latest version in the construction call.
+Increment the "latest" version whenever the structure changes, and try to only
+append to the end of the structure.
+
+## Option Structures
+
+If a function's parameter count is too high, it may be desirable to package
+up the options in a structure. Make them transparent, include a version
+field, and provide an initializer constant or constructor. Using these
+structures should be this easy:
+
+```C
+git_foo_options opts = GIT_FOO_OPTIONS_INIT;
+opts.baz = BAZ_OPTION_ONE;
+git_foo(&opts);
+```
+
+## Enumerations
+
+Typedef all enumerated types. If each option stands alone, use the enum
+type for passing them as parameters; if they are flags to be OR'ed together,
+pass them as `unsigned int` or `uint32_t` or some appropriate type.
+
+## Code Layout
+
+Try to keep lines less than 80 characters long. This is a loose
+requirement, but going significantly over 80 columns is not nice.
+
+Use common sense to wrap most code lines; public function declarations
+can use a couple of different styles:
+
+```c
+/** All on one line is okay if it fits */
+GIT_EXTERN(int) git_foo_simple(git_oid *id);
+
+/** Otherwise one argument per line is a good next step */
+GIT_EXTERN(int) git_foo_id(
+ git_oid **out,
+ int a,
+ int b);
+```
+
+Indent with tabs; set your editor's tab width to 4 for best effect.
+
+Avoid trailing whitespace and only commit Unix-style newlines (i.e. no CRLF
+in the repository - just set `core.autocrlf` to true if you are writing code
+on a Windows machine).
+
+## Documentation
+
+All comments should conform to Doxygen "javadoc" style conventions for
+formatting the public API documentation. Try to document every parameter,
+and keep the comments up to date if you change the parameter list.
+
+## Public Header Template
+
+Use this template when creating a new public header.
+
+```C
+#ifndef INCLUDE_git_${filename}_h__
+#define INCLUDE_git_${filename}_h__
+
+#include "git/common.h"
+
+/**
+ * @file git/${filename}.h
+ * @brief Git some description
+ * @defgroup git_${filename} some description routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/* ... definitions ... */
+
+/** @} */
+GIT_END_DECL
+#endif
+```
+
+## Inlined functions
+
+All inlined functions must be declared as:
+
+```C
+GIT_INLINE(result_type) git_modulename_functionname(arg_list);
+```
+
+`GIT_INLINE` (or `inline`) should not be used in public headers in order
+to preserve ANSI C compatibility.
+
+## Tests
+
+`libgit2` uses the [clar](https://github.com/vmg/clar) testing framework.
+
+All PRs should have corresponding tests.
+
+* If the PR fixes an existing issue, the test should fail prior to applying
+ the PR and succeed after applying it.
+* If the PR is for new functionality, then the tests should exercise that
+ new functionality to a certain extent. We don't require 100% coverage
+ right now (although we are getting stricter over time).
+
+When adding new tests, we prefer if you attempt to reuse existing test data
+(in `tests-clar/resources/`) if possible. If you are going to add new test
+repositories, please try to strip them of unnecessary files (e.g. sample
+hooks, etc).
--- /dev/null
+ libgit2 is Copyright (C) the libgit2 contributors,
+ unless otherwise stated. See the AUTHORS file for details.
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+----------------------------------------------------------------------
+
+ LINKING EXCEPTION
+
+ In addition to the permissions in the GNU General Public License,
+ the authors give you unlimited permission to link the compiled
+ version of this library into combinations with other programs,
+ and to distribute those combinations without any restriction
+ coming from the use of this file. (The General Public License
+ restrictions do apply in other respects; for example, they cover
+ modification of the file, and distribution when not linked into
+ a combined executable.)
+
+----------------------------------------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
+----------------------------------------------------------------------
+
+The bundled ZLib code is licensed under the ZLib license:
+
+Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup@gzip.org madler@alumni.caltech.edu
+
+----------------------------------------------------------------------
+
+The Clar framework is licensed under the ISC license:
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+----------------------------------------------------------------------
+
+The regex library (deps/regex/) is licensed under the GNU LGPL
+(available at the end of this file).
+
+Definitions for data structures and routines for the regular
+expression library.
+
+Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008
+Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with the GNU C Library; if not, write to the Free
+Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+----------------------------------------------------------------------
+
+The bundled winhttp definition files (deps/winhttp/) are licensed under
+the GNU LGPL (available at the end of this file).
+
+Copyright (C) 2007 Francois Gouget
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+----------------------------------------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+\f
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+----------------------------------------------------------------------
--- /dev/null
+Projects For LibGit2
+====================
+
+So, you want to start helping out with `libgit2`? That's fantastic! We
+welcome contributions and we promise we'll try to be nice.
+
+This is a list of libgit2 related projects that new contributors can take
+on. It includes a number of good starter projects as well as some larger
+ideas that no one is actively working on.
+
+## Before You Start
+
+Please start by reading the [README.md](README.md),
+[CONTRIBUTING.md](CONTRIBUTING.md), and [CONVENTIONS.md](CONVENTIONS.md)
+files before diving into one of these projects. Those explain our work
+flow and coding conventions to help ensure that your work will be easily
+integrated into libgit2.
+
+Next, work through the build instructions and make sure you can clone the
+repository, compile it, and run the tests successfully. That will make
+sure that your development environment is set up correctly and you are
+ready to start on libgit2 development.
+
+## Starter Projects
+
+These are good small projects to get started with libgit2.
+
+* Look at the `examples/` programs, find an existing one that mirrors a
+ core Git command and add a missing command-line option. There are many
+ gaps right now and this helps demonstrate how to use the library. Here
+ are some specific ideas (though there are many more):
+ * Fix the `examples/diff.c` implementation of the `-B`
+ (a.k.a. `--break-rewrites`) command line option to actually look for
+ the optional `[<n>][/<m>]` configuration values. There is an
+ existing comment that reads `/* TODO: parse thresholds */`. The
+ trick to this one will be doing it in a manner that is clean and
+ simple, but still handles the various cases correctly (e.g. `-B/70%`
+ is apparently a legal setting).
+ * Implement the `--log-size` option for `examples/log.c`. I think all
+ the data is available, you would just need to add the code into the
+ `print_commit()` routine (along with a way of passing the option
+ into that function).
+ * As an extension to the matching idea for `examples/log.c`, add the
+ `-i` option to use `strcasestr()` for matches.
+ * For `examples/log.c`, implement the `--first-parent` option now that
+ libgit2 supports it in the revwalk API.
+* Pick a Git command that is not already emulated in `examples/` and write
+ a new example that mirrors the behavior. Examples don't have to be
+ perfect emulations, but should demonstrate how to use the libgit2 APIs
+ to get results that are similar to Git commands. This lets you (and us)
+ easily exercise a particular facet of the API and measure compatibility
+ and feature parity with core git.
+* Submit a PR to clarify documentation! While we do try to document all of
+ the APIs, your fresh eyes on the documentation will find areas that are
+ confusing much more easily.
+
+If none of these appeal to you, take a look at our issues list to see if
+there are any unresolved issues you'd like to jump in on.
+
+## Larger Projects
+
+These are ideas for larger projects mostly taken from our backlog of
+[Issues](https://github.com/libgit2/libgit2/issues). Please don't dive
+into one of these as a first project for libgit2 - we'd rather get to
+know you first by successfully shipping your work on one of the smaller
+projects above.
+
+Some of these projects are broken down into subprojects and/or have
+some incremental steps listed towards the larger goal. Those steps
+might make good smaller projects by themselves.
+
+* Port part of the Git test suite to run against the command line emulation
+ in `examples/`
+ * Pick a Git command that is emulated in our `examples/` area
+ * Extract the Git tests that exercise that command
+ * Convert the tests to call our emulation
+ * These tests could go in `examples/tests/`...
+* Add hooks API to enumerate and manage hooks (not run them at this point)
+ * Enumeration of available hooks
+ * Lookup API to see which hooks have a script and get the script
+ * Read/write API to load a hook script and write a hook script
+ * Eventually, callback API to invoke a hook callback when libgit2
+ executes the action in question
+* Isolate logic of ignore evaluation into a standalone API
+* Upgrade internal libxdiff code to latest from core Git
+* Tree builder improvements:
+ * Extend to allow building a tree hierarchy
+* Apply-patch API
+* Add a patch editing API to enable "git add -p" type operations
+* Textconv API to filter binary data before generating diffs (something
+ like the current Filter API, probably).
+* Performance profiling and improvement
+* Support "git replace" ref replacements
+* Include conflicts in diff results and in status
+ * GIT_DELTA_CONFLICT for items in conflict (with multiple files)
+ * Appropriate flags for status
+* Support sparse checkout (i.e. "core.sparsecheckout" and ".git/info/sparse-checkout")
--- /dev/null
+libgit2 - the Git linkable library
+==================================
+
+[](http://travis-ci.org/libgit2/libgit2)
+[](https://ci.appveyor.com/project/libgit2/libgit2/branch/master)
+[](https://scan.coverity.com/projects/639)
+
+`libgit2` is a portable, pure C implementation of the Git core methods
+provided as a re-entrant linkable library with a solid API, allowing you to
+write native speed custom Git applications in any language with bindings.
+
+`libgit2` is licensed under a **very permissive license** (GPLv2 with a special
+Linking Exception). This basically means that you can link it (unmodified)
+with any kind of software without having to release its source code.
+Additionally, the example code has been released to the public domain (see the
+[separate license](examples/COPYING) for more information).
+
+Getting Help
+============
+
+**Join us on Slack**
+
+Visit [slack.libgit2.org](http://slack.libgit2.org/) to sign up, then join
+us in `#libgit2`. If you prefer IRC, you can also point your client to our
+slack channel once you've registered.
+
+**Getting Help**
+
+If you have questions about the library, please be sure to check out the
+[API documentation](http://libgit2.github.com/libgit2/). If you still have
+questions, reach out to us on Slack or post a question on
+[StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) (with the `libgit2` tag).
+
+**Reporting Bugs**
+
+Please open a [GitHub Issue](https://github.com/libgit2/libgit2/issues) and
+include as much information as possible. If possible, provide sample code
+that illustrates the problem you're seeing. If you're seeing a bug only
+on a specific repository, please provide a link to it if possible.
+
+We ask that you not open a GitHub Issue for help, only for bug reports.
+
+What It Can Do
+==============
+
+The goal of this library is to allow its users the ability to handle Git data in
+their applications from their programming language of choice, as is used in
+production for many applications including the GitHub.com site, in Plastic SCM
+and also powering Microsoft's Visual Studio tools for Git.
+
+It does not aim to replace the git tool or its user-facing commands. Some APIs
+resemble the plumbing commands as those align closely with the concepts of the
+Git system, but most commands a user would type are out of scope for this
+library to implement directly.
+
+The library provides:
+
+* SHA conversions, formatting and shortening
+* abstracted ODB backend system
+* commit, tag, tree and blob parsing, editing, and write-back
+* tree traversal
+* revision walking
+* index file (staging area) manipulation
+* reference management (including packed references)
+* config file management
+* high level repository management
+* thread safety and reentrancy
+* descriptive and detailed error messages
+* ...and more (over 175 different API calls)
+
+Optional dependencies
+=====================
+
+While the library provides git functionality without the need for
+dependencies, it can make use of a few libraries to add to it:
+
+- pthreads (non-Windows) to enable threadsafe access as well as multi-threaded pack generation
+- OpenSSL (non-Windows) to talk over HTTPS and provide the SHA-1 functions
+- LibSSH2 to enable the SSH transport
+- iconv (OSX) to handle the HFS+ path encoding peculiarities
+
+Initialization
+===============
+
+The library needs to keep track of some global state. Call
+
+ git_libgit2_init();
+
+before calling any other libgit2 functions. You can call this function many times. A matching number of calls to
+
+ git_libgit2_shutdown();
+
+will free the resources. Note that if you have worker threads, you should
+call `git_libgit2_shutdown` *after* those threads have exited. If you
+require assistance coordinating this, simply have the worker threads call
+`git_libgit2_init` at startup and `git_libgit2_shutdown` at shutdown.
+
+Threading
+=========
+
+See [THREADING](THREADING.md) for information
+
+Conventions
+===========
+
+See [CONVENTIONS](CONVENTIONS.md) for an overview of the external
+and internal API/coding conventions we use.
+
+Building libgit2 - Using CMake
+==============================
+
+`libgit2` builds cleanly on most platforms without any external dependencies.
+Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available;
+they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API
+for threading.
+
+The `libgit2` library is built using [CMake](<https://cmake.org/>) (version 2.8 or newer) on all platforms.
+
+On most systems you can build the library using the following commands
+
+ $ mkdir build && cd build
+ $ cmake ..
+ $ cmake --build .
+
+Alternatively you can point the CMake GUI tool to the CMakeLists.txt file and generate platform specific build project or IDE workspace.
+
+To install the library you can specify the install prefix by setting:
+
+ $ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix
+ $ cmake --build . --target install
+
+For more advanced use or questions about CMake please read <https://cmake.org/Wiki/CMake_FAQ>.
+
+The following CMake variables are declared:
+
+- `BIN_INSTALL_DIR`: Where to install binaries to.
+- `LIB_INSTALL_DIR`: Where to install libraries to.
+- `INCLUDE_INSTALL_DIR`: Where to install headers to.
+- `BUILD_SHARED_LIBS`: Build libgit2 as a Shared Library (defaults to ON)
+- `BUILD_CLAR`: Build [Clar](https://github.com/vmg/clar)-based test suite (defaults to ON)
+- `THREADSAFE`: Build libgit2 with threading support (defaults to ON)
+- `STDCALL`: Build libgit2 as `stdcall`. Turn off for `cdecl` (Windows; defaults to ON)
+
+Compiler and linker options
+---------------------------
+
+CMake lets you specify a few variables to control the behavior of the
+compiler and linker. These flags are rarely used but can be useful for
+64-bit to 32-bit cross-compilation.
+
+- `CMAKE_C_FLAGS`: Set your own compiler flags
+- `CMAKE_FIND_ROOT_PATH`: Override the search path for libraries
+- `ZLIB_LIBRARY`, `OPENSSL_SSL_LIBRARY` AND `OPENSSL_CRYPTO_LIBRARY`:
+Tell CMake where to find those specific libraries
+
+MacOS X
+-------
+
+If you want to build a universal binary for Mac OS X, CMake sets it
+all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"`
+when configuring.
+
+Windows
+-------
+
+You need to run the CMake commands from the Visual Studio command
+prompt, not the regular or Windows SDK one. Select the right generator
+for your version with the `-G "Visual Studio X" option.
+
+See [the website](http://libgit2.github.com/docs/guides/build-and-link/)
+for more detailed instructions.
+
+Android
+-------
+
+Extract toolchain from NDK using, `make-standalone-toolchain.sh` script.
+Optionally, crosscompile and install OpenSSL inside of it. Then create CMake
+toolchain file that configures paths to your crosscompiler (substitute `{PATH}`
+with full path to the toolchain):
+
+ SET(CMAKE_SYSTEM_NAME Linux)
+ SET(CMAKE_SYSTEM_VERSION Android)
+
+ SET(CMAKE_C_COMPILER {PATH}/bin/arm-linux-androideabi-gcc)
+ SET(CMAKE_CXX_COMPILER {PATH}/bin/arm-linux-androideabi-g++)
+ SET(CMAKE_FIND_ROOT_PATH {PATH}/sysroot/)
+
+ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+ SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+ SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+Add `-DCMAKE_TOOLCHAIN_FILE={pathToToolchainFile}` to cmake command
+when configuring.
+
+Language Bindings
+==================================
+
+Here are the bindings to libgit2 that are currently available:
+
+* C++
+ * libqgit2, Qt bindings <https://projects.kde.org/projects/playground/libs/libqgit2/repository/>
+* Chicken Scheme
+ * chicken-git <https://wiki.call-cc.org/egg/git>
+* D
+ * dlibgit <https://github.com/s-ludwig/dlibgit>
+* Delphi
+ * GitForDelphi <https://github.com/libgit2/GitForDelphi>
+* Erlang
+ * Geef <https://github.com/carlosmn/geef>
+* Go
+ * git2go <https://github.com/libgit2/git2go>
+* GObject
+ * libgit2-glib <https://wiki.gnome.org/Projects/Libgit2-glib>
+* Haskell
+ * hgit2 <https://github.com/jwiegley/gitlib>
+* Java
+ * Jagged <https://github.com/ethomson/jagged>
+* Julia
+ * LibGit2.jl <https://github.com/jakebolewski/LibGit2.jl>
+* Lua
+ * luagit2 <https://github.com/libgit2/luagit2>
+* .NET
+ * libgit2sharp <https://github.com/libgit2/libgit2sharp>
+* Node.js
+ * nodegit <https://github.com/nodegit/nodegit>
+* Objective-C
+ * objective-git <https://github.com/libgit2/objective-git>
+* OCaml
+ * ocaml-libgit2 <https://github.com/fxfactorial/ocaml-libgit2>
+* Parrot Virtual Machine
+ * parrot-libgit2 <https://github.com/letolabs/parrot-libgit2>
+* Perl
+ * Git-Raw <https://github.com/jacquesg/p5-Git-Raw>
+* PHP
+ * php-git <https://github.com/libgit2/php-git>
+* PowerShell
+ * PSGit <https://github.com/PoshCode/PSGit>
+* Python
+ * pygit2 <https://github.com/libgit2/pygit2>
+* R
+ * git2r <https://github.com/ropensci/git2r>
+* Ruby
+ * Rugged <https://github.com/libgit2/rugged>
+* Rust
+ * git2-rs <https://github.com/alexcrichton/git2-rs>
+* Swift
+ * SwiftGit2 <https://github.com/SwiftGit2/SwiftGit2>
+* Vala
+ * libgit2.vapi <https://github.com/apmasell/vapis/blob/master/libgit2.vapi>
+
+If you start another language binding to libgit2, please let us know so
+we can add it to the list.
+
+How Can I Contribute?
+==================================
+
+We welcome new contributors! We have a number of issues marked as
+["up for grabs"](https://github.com/libgit2/libgit2/issues?q=is%3Aissue+is%3Aopen+label%3A%22up+for+grabs%22)
+and
+["easy fix"](https://github.com/libgit2/libgit2/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3A%22easy+fix%22)
+that are good places to jump in and get started. There's much more detailed
+information in our list of [outstanding projects](PROJECTS.md).
+
+Please be sure to check the [contribution guidelines](CONTRIBUTING.md) to
+understand our workflow, and the libgit2 [coding conventions](CONVENTIONS.md).
+
+License
+==================================
+
+`libgit2` is under GPL2 **with linking exception**. This means you can link to
+and use the library from any program, proprietary or open source; paid or
+gratis. However, if you modify libgit2 itself, you must distribute the
+source to your modified version of libgit2.
+
+See the [COPYING file](COPYING) for the full license text.
--- /dev/null
+Threads in libgit2
+==================
+
+You may safely use any libgit2 object from any thread, though there
+may be issues depending on the cryptographic libraries libgit2 or its
+dependencies link to (more on this later). For libgit2 itself,
+provided you take the following into consideration you won't run into
+issues:
+
+Sharing objects
+---------------
+
+Use an object from a single thread at a time. Most data structures do
+not guard against concurrent access themselves. This is because they
+are rarely used in isolation and it makes more sense to synchronize
+access via a larger lock or similar mechanism.
+
+There are some objects which are read-only/immutable and are thus safe
+to share across threads, such as references and configuration
+snapshots.
+
+Error messages
+--------------
+
+The error message is thread-local. The `giterr_last()` call must
+happen on the same thread as the error in order to get the
+message. Often this will be the case regardless, but if you use
+something like the [GCD](http://en.wikipedia.org/wiki/Grand_Central_Dispatch)
+on Mac OS X (where code is executed on an arbitrary thread), the code
+must make sure to retrieve the error code on the thread where the error
+happened.
+
+Threads and cryptographic libraries
+=======================================
+
+On Windows
+----------
+
+When built as a native Windows DLL, libgit2 uses WinCNG and WinHTTP,
+both of which are thread-safe. You do not need to do anything special.
+
+When using libssh2 which itself uses WinCNG, there are no special
+steps necessary. If you are using a MinGW or similar environment where
+libssh2 uses OpenSSL or libgcrypt, then the general case affects
+you.
+
+On Mac OS X
+-----------
+
+By default we use libcurl to perform the encryption. The
+system-provided libcurl uses SecureTransport, so no special steps are
+necessary. If you link against another libcurl (e.g. from homebrew)
+refer to the general case.
+
+If the option to use libcurl was deactivated, the library makes use of
+CommonCrypto and SecureTransport for cryptographic support. These are
+thread-safe and you do not need to do anything special.
+
+Note that libssh2 may still use OpenSSL itself. In that case, the
+general case still affects you if you use ssh.
+
+General Case
+------------
+
+If it's available, by default we use libcurl to provide HTTP tunneling support,
+which may be linked against a number of cryptographic libraries and has its
+own
+[recommendations for thread safety](https://curl.haxx.se/libcurl/c/threadsafe.html).
+
+If there are no alternative TLS implementations (currently only
+SecureTransport), libgit2 uses OpenSSL in order to use HTTPS as a transport.
+OpenSSL is thread-safe starting at version 1.1.0. If your copy of libgit2 is
+linked against that version, you do not need to take any further steps.
+
+Older versions of OpenSSL are made to be thread-implementation agnostic, and the
+users of the library must set which locking function it should use. libgit2
+cannot know what to set as the user of libgit2 may also be using OpenSSL independently and
+the locking settings must then live outside the lifetime of libgit2.
+
+Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used by
+libssh2 or libcurl depending on the configuration. If OpenSSL is used by
+more than one library, you only need to set up threading for OpenSSL once.
+
+If libgit2 is linked against OpenSSL, it provides a last-resort convenience function
+`git_openssl_set_locking()` (available in `sys/openssl.h`) to use the
+platform-native mutex mechanisms to perform the locking, which you can use
+if you do not want to use OpenSSL outside of libgit2, or you
+know that libgit2 will outlive the rest of the operations. It is then not
+safe to use OpenSSL multi-threaded after libgit2's shutdown function
+has been called. Note `git_openssl_set_locking()` only works if
+libgit2 uses OpenSSL directly - if OpenSSL is only used as a dependency
+of libssh2 or libcurl as described above, `git_openssl_set_locking()` is a no-op.
+
+If your programming language offers a package/bindings for OpenSSL,
+you should very strongly prefer to use that in order to set up
+locking, as they provide a level of coordination which is impossible
+when using this function.
+
+See the
+[OpenSSL documentation](https://www.openssl.org/docs/crypto/threads.html)
+on threading for more details, and http://trac.libssh2.org/wiki/MultiThreading
+for a specific example of providing the threading callbacks.
+
+libssh2 may be linked against OpenSSL or libgcrypt. If it uses OpenSSL,
+see the above paragraphs. If it uses libgcrypt, then you need to
+set up its locking before using it multi-threaded. libgit2 has no
+direct connection to libgcrypt and thus has no convenience functions for
+it (but libgcrypt has macros). Read libgcrypt's
+[threading documentation for more information](http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html)
+
+It is your responsibility as an application author or packager to know
+what your dependencies are linked against and to take the appropriate
+steps to ensure the cryptographic libraries are thread-safe. We agree
+that this situation is far from ideal but at this time it is something
+the application authors need to deal with.
--- /dev/null
+{
+ "name": "libgit2",
+ "github": "libgit2/libgit2",
+ "input": "include/git2",
+ "prefix": "git_",
+ "output": "docs",
+ "branch": "gh-pages",
+ "examples": "examples",
+ "legacy": {
+ "input": {"src/git": ["v0.1.0"],
+ "src/git2": ["v0.2.0", "v0.3.0"]}
+ }
+}
--- /dev/null
+version: '{build}'
+branches:
+ only:
+ - master
+ - /^maint.*/
+environment:
+ GITTEST_INVASIVE_FS_STRUCTURE: 1
+ GITTEST_INVASIVE_FS_SIZE: 1
+
+ matrix:
+ - GENERATOR: "Visual Studio 11"
+ ARCH: 32
+ - GENERATOR: "Visual Studio 11 Win64"
+ ARCH: 64
+ - GENERATOR: "MSYS Makefiles"
+ ARCH: i686 # this is for 32-bit MinGW-w64
+ - GENERATOR: "MSYS Makefiles"
+ ARCH: 64
+cache:
+- i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z
+- x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z
+
+build_script:
+- ps: |
+ mkdir build
+ cd build
+ if ($env:GENERATOR -ne "MSYS Makefiles") {
+ cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D MSVC_CRTDBG=ON .. -G"$env:GENERATOR"
+ cmake --build . --config Debug
+ }
+- cmd: |
+ if "%GENERATOR%"=="MSYS Makefiles" (C:\MinGW\msys\1.0\bin\sh --login /c/projects/libgit2/script/appveyor-mingw.sh)
+test_script:
+- ps: |
+ $ErrorActionPreference="Stop"
+ Start-FileDownload https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -FileName poxyproxy.jar
+ # Run this early so we know it's ready by the time we need it
+ $proxyJob = Start-Job { java -jar $Env:APPVEYOR_BUILD_FOLDER\build\poxyproxy.jar -d --port 8080 --credentials foo:bar }
+ ctest -V -R libgit2_clar
+ $env:GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent"
+ $env:GITTEST_REMOTE_USER="libgit2test"
+ ctest -V -R libgit2_clar-cred_callback
+ Receive-Job -Job $proxyJob
+ $env:GITTEST_REMOTE_PROXY_URL = "http://foo:bar@localhost:8080"
+ ctest -V -R libgit2_clar-proxy_credentials_in_url
+ $env:GITTEST_REMOTE_PROXY_URL = "http://localhost:8080"
+ $env:GITTEST_REMOTE_PROXY_USER = "foo"
+ $env:GITTEST_REMOTE_PROXY_PASS = "bar"
+ ctest -V -R libgit2_clar-proxy_credentials_request
--- /dev/null
+# - Append compiler flag to CMAKE_C_FLAGS if compiler supports it
+# ADD_C_FLAG_IF_SUPPORTED(<flag>)
+# <flag> - the compiler flag to test
+# This internally calls the CHECK_C_COMPILER_FLAG macro.
+
+INCLUDE(CheckCCompilerFlag)
+
+MACRO(ADD_C_FLAG_IF_SUPPORTED _FLAG)
+ STRING(TOUPPER ${_FLAG} UPCASE)
+ STRING(REGEX REPLACE "^-" "" UPCASE_PRETTY ${UPCASE})
+ CHECK_C_COMPILER_FLAG(${_FLAG} IS_${UPCASE_PRETTY}_SUPPORTED)
+
+ IF(IS_${UPCASE_PRETTY}_SUPPORTED)
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FLAG}")
+ ENDIF()
+ENDMACRO()
--- /dev/null
+IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS)
+ SET(COREFOUNDATION_FOUND TRUE)
+ELSE ()
+ FIND_PATH(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h)
+ FIND_LIBRARY(COREFOUNDATION_DIRS NAMES CoreFoundation)
+ IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS)
+ SET(COREFOUNDATION_FOUND TRUE)
+ ENDIF ()
+ENDIF ()
--- /dev/null
+# - Try to find GSSAPI
+# Once done this will define
+#
+# KRB5_CONFIG - Path to krb5-config
+# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI
+#
+# Read-Only variables:
+# GSSAPI_FLAVOR_MIT - set to TURE if MIT Kerberos has been found
+# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found
+# GSSAPI_FOUND - system has GSSAPI
+# GSSAPI_INCLUDE_DIR - the GSSAPI include directory
+# GSSAPI_LIBRARIES - Link these to use GSSAPI
+# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI
+#
+#=============================================================================
+# Copyright (c) 2013 Andreas Schneider <asn@cryptomilk.org>
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+#
+
+find_path(GSSAPI_ROOT_DIR
+ NAMES
+ include/gssapi.h
+ include/gssapi/gssapi.h
+ HINTS
+ ${_GSSAPI_ROOT_HINTS}
+ PATHS
+ ${_GSSAPI_ROOT_PATHS}
+)
+mark_as_advanced(GSSAPI_ROOT_DIR)
+
+if (UNIX)
+ find_program(KRB5_CONFIG
+ NAMES
+ krb5-config
+ PATHS
+ ${GSSAPI_ROOT_DIR}/bin
+ /opt/local/bin)
+ mark_as_advanced(KRB5_CONFIG)
+
+ if (KRB5_CONFIG)
+ # Check if we have MIT KRB5
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --vendor
+ RESULT_VARIABLE
+ _GSSAPI_VENDOR_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_VENDOR_STRING)
+
+ if (_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*")
+ set(GSSAPI_FLAVOR_MIT TRUE)
+ else()
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --libs gssapi
+ RESULT_VARIABLE
+ _GSSAPI_LIBS_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_LIBS_STRING)
+
+ if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*")
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ endif()
+ endif()
+
+ # Get the include dir
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --cflags gssapi
+ RESULT_VARIABLE
+ _GSSAPI_INCLUDE_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_INCLUDE_STRING)
+ string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}")
+ string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}")
+ endif()
+
+ if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL)
+ # Check for HEIMDAL
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(_GSSAPI heimdal-gssapi)
+ endif (PKG_CONFIG_FOUND)
+
+ if (_GSSAPI_FOUND)
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ else()
+ find_path(_GSSAPI_ROKEN
+ NAMES
+ roken.h
+ PATHS
+ ${GSSAPI_ROOT_DIR}/include
+ ${_GSSAPI_INCLUDEDIR})
+ if (_GSSAPI_ROKEN)
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ endif()
+ endif ()
+ endif()
+endif (UNIX)
+
+find_path(GSSAPI_INCLUDE_DIR
+ NAMES
+ gssapi.h
+ gssapi/gssapi.h
+ PATHS
+ ${GSSAPI_ROOT_DIR}/include
+ ${_GSSAPI_INCLUDEDIR}
+)
+
+if (GSSAPI_FLAVOR_MIT)
+ find_library(GSSAPI_LIBRARY
+ NAMES
+ gssapi_krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(KRB5_LIBRARY
+ NAMES
+ krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(K5CRYPTO_LIBRARY
+ NAMES
+ k5crypto
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(COM_ERR_LIBRARY
+ NAMES
+ com_err
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ if (GSSAPI_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${GSSAPI_LIBRARY}
+ )
+ endif (GSSAPI_LIBRARY)
+
+ if (KRB5_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${KRB5_LIBRARY}
+ )
+ endif (KRB5_LIBRARY)
+
+ if (K5CRYPTO_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${K5CRYPTO_LIBRARY}
+ )
+ endif (K5CRYPTO_LIBRARY)
+
+ if (COM_ERR_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${COM_ERR_LIBRARY}
+ )
+ endif (COM_ERR_LIBRARY)
+endif (GSSAPI_FLAVOR_MIT)
+
+if (GSSAPI_FLAVOR_HEIMDAL)
+ find_library(GSSAPI_LIBRARY
+ NAMES
+ gssapi
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(KRB5_LIBRARY
+ NAMES
+ krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HCRYPTO_LIBRARY
+ NAMES
+ hcrypto
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(COM_ERR_LIBRARY
+ NAMES
+ com_err
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HEIMNTLM_LIBRARY
+ NAMES
+ heimntlm
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HX509_LIBRARY
+ NAMES
+ hx509
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(ASN1_LIBRARY
+ NAMES
+ asn1
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(WIND_LIBRARY
+ NAMES
+ wind
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(ROKEN_LIBRARY
+ NAMES
+ roken
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ if (GSSAPI_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${GSSAPI_LIBRARY}
+ )
+ endif (GSSAPI_LIBRARY)
+
+ if (KRB5_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${KRB5_LIBRARY}
+ )
+ endif (KRB5_LIBRARY)
+
+ if (HCRYPTO_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HCRYPTO_LIBRARY}
+ )
+ endif (HCRYPTO_LIBRARY)
+
+ if (COM_ERR_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${COM_ERR_LIBRARY}
+ )
+ endif (COM_ERR_LIBRARY)
+
+ if (HEIMNTLM_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HEIMNTLM_LIBRARY}
+ )
+ endif (HEIMNTLM_LIBRARY)
+
+ if (HX509_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HX509_LIBRARY}
+ )
+ endif (HX509_LIBRARY)
+
+ if (ASN1_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${ASN1_LIBRARY}
+ )
+ endif (ASN1_LIBRARY)
+
+ if (WIND_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${WIND_LIBRARY}
+ )
+ endif (WIND_LIBRARY)
+
+ if (ROKEN_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${WIND_LIBRARY}
+ )
+ endif (ROKEN_LIBRARY)
+endif (GSSAPI_FLAVOR_HEIMDAL)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR)
+
+if (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
+ set(GSSAPI_FOUND TRUE)
+endif (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
+
+# show the GSSAPI_INCLUDE_DIRS and GSSAPI_LIBRARIES variables only in the advanced view
+mark_as_advanced(GSSAPI_INCLUDE_DIRS GSSAPI_LIBRARIES)
--- /dev/null
+# - Try to find http-parser
+#
+# Defines the following variables:
+#
+# HTTP_PARSER_FOUND - system has http-parser
+# HTTP_PARSER_INCLUDE_DIR - the http-parser include directory
+# HTTP_PARSER_LIBRARIES - Link these to use http-parser
+# HTTP_PARSER_VERSION_MAJOR - major version
+# HTTP_PARSER_VERSION_MINOR - minor version
+# HTTP_PARSER_VERSION_STRING - the version of http-parser found
+
+# Find the header and library
+FIND_PATH(HTTP_PARSER_INCLUDE_DIR NAMES http_parser.h)
+FIND_LIBRARY(HTTP_PARSER_LIBRARY NAMES http_parser libhttp_parser)
+
+# Found the header, read version
+if (HTTP_PARSER_INCLUDE_DIR AND EXISTS "${HTTP_PARSER_INCLUDE_DIR}/http_parser.h")
+ FILE(READ "${HTTP_PARSER_INCLUDE_DIR}/http_parser.h" HTTP_PARSER_H)
+ IF (HTTP_PARSER_H)
+ STRING(REGEX REPLACE ".*#define[\t ]+HTTP_PARSER_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" HTTP_PARSER_VERSION_MAJOR "${HTTP_PARSER_H}")
+ STRING(REGEX REPLACE ".*#define[\t ]+HTTP_PARSER_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" HTTP_PARSER_VERSION_MINOR "${HTTP_PARSER_H}")
+ SET(HTTP_PARSER_VERSION_STRING "${HTTP_PARSER_VERSION_MAJOR}.${HTTP_PARSER_VERSION_MINOR}")
+ ENDIF()
+ UNSET(HTTP_PARSER_H)
+ENDIF()
+
+# Handle the QUIETLY and REQUIRED arguments and set HTTP_PARSER_FOUND
+# to TRUE if all listed variables are TRUE
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(HTTP_Parser REQUIRED_VARS HTTP_PARSER_INCLUDE_DIR HTTP_PARSER_LIBRARY)
+
+# Hide advanced variables
+MARK_AS_ADVANCED(HTTP_PARSER_INCLUDE_DIR HTTP_PARSER_LIBRARY)
+
+# Set standard variables
+IF (HTTP_PARSER_FOUND)
+ SET(HTTP_PARSER_LIBRARIES ${HTTP_PARSER_LIBRARY})
+ set(HTTP_PARSER_INCLUDE_DIRS ${HTTP_PARSER_INCLUDE_DIR})
+ENDIF()
--- /dev/null
+# - Try to find Iconv
+# Once done this will define
+#
+# ICONV_FOUND - system has Iconv
+# ICONV_INCLUDE_DIR - the Iconv include directory
+# ICONV_LIBRARIES - Link these to use Iconv
+#
+
+IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES)
+ # Already in cache, be silent
+ SET(ICONV_FIND_QUIETLY TRUE)
+ENDIF()
+
+FIND_PATH(ICONV_INCLUDE_DIR iconv.h)
+FIND_LIBRARY(iconv_lib NAMES iconv libiconv libiconv-2 c)
+
+IF(ICONV_INCLUDE_DIR AND iconv_lib)
+ SET(ICONV_FOUND TRUE)
+ENDIF()
+
+IF(ICONV_FOUND)
+ #Â split iconv into -L and -l linker options, so we can set them for pkg-config
+ GET_FILENAME_COMPONENT(iconv_path ${iconv_lib} PATH)
+ GET_FILENAME_COMPONENT(iconv_name ${iconv_lib} NAME_WE)
+ STRING(REGEX REPLACE "^lib" "" iconv_name ${iconv_name})
+ SET(ICONV_LIBRARIES "-L${iconv_path} -l${iconv_name}")
+
+ IF(NOT ICONV_FIND_QUIETLY)
+ MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}")
+ ENDIF(NOT ICONV_FIND_QUIETLY)
+ELSE()
+ IF(Iconv_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find Iconv")
+ ENDIF(Iconv_FIND_REQUIRED)
+ENDIF()
+
+MARK_AS_ADVANCED(
+ ICONV_INCLUDE_DIR
+ ICONV_LIBRARIES
+)
--- /dev/null
+IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS)
+ SET(SECURITY_FOUND TRUE)
+ELSE ()
+ FIND_PATH(SECURITY_INCLUDE_DIR NAMES Security/Security.h)
+ FIND_LIBRARY(SECURITY_DIRS NAMES Security)
+ IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS)
+ SET(SECURITY_FOUND TRUE)
+ ENDIF ()
+ENDIF ()
--- /dev/null
+Checkout Internals
+==================
+
+Checkout has to handle a lot of different cases. It examines the
+differences between the target tree, the baseline tree and the working
+directory, plus the contents of the index, and groups files into five
+categories:
+
+1. UNMODIFIED - Files that match in all places.
+2. SAFE - Files where the working directory and the baseline content
+ match that can be safely updated to the target.
+3. DIRTY/MISSING - Files where the working directory differs from the
+ baseline but there is no conflicting change with the target. One
+ example is a file that doesn't exist in the working directory - no
+ data would be lost as a result of writing this file. Which action
+ will be taken with these files depends on the options you use.
+4. CONFLICTS - Files where changes in the working directory conflict
+ with changes to be applied by the target. If conflicts are found,
+ they prevent any other modifications from being made (although there
+ are options to override that and force the update, of course).
+5. UNTRACKED/IGNORED - Files in the working directory that are untracked
+ or ignored (i.e. only in the working directory, not the other places).
+
+Right now, this classification is done via 3 iterators (for the three
+trees), with a final lookup in the index. At some point, this may move to
+a 4 iterator version to incorporate the index better.
+
+The actual checkout is done in five phases (at least right now).
+
+1. The diff between the baseline and the target tree is used as a base
+ list of possible updates to be applied.
+2. Iterate through the diff and the working directory, building a list of
+ actions to be taken (and sending notifications about conflicts and
+ dirty files).
+3. Remove any files / directories as needed (because alphabetical
+ iteration means that an untracked directory will end up sorted *after*
+ a blob that should be checked out with the same name).
+4. Update all blobs.
+5. Update all submodules (after 4 in case a new .gitmodules blob was
+ checked out)
+
+Checkout could be driven either off a target-to-workdir diff or a
+baseline-to-target diff. There are pros and cons of each.
+
+Target-to-workdir means the diff includes every file that could be
+modified, which simplifies bookkeeping, but the code to constantly refer
+back to the baseline gets complicated.
+
+Baseline-to-target has simpler code because the diff defines the action to
+take, but needs special handling for untracked and ignored files, if they
+need to be removed.
+
+The current checkout implementation is based on a baseline-to-target diff.
+
+
+Picking Actions
+===============
+
+The most interesting aspect of this is phase 2, picking the actions that
+should be taken. There are a lot of corner cases, so it may be easier to
+start by looking at the rules for a simple 2-iterator diff:
+
+Key
+---
+- B1,B2,B3 - blobs with different SHAs,
+- Bi - ignored blob (WD only)
+- T1,T2,T3 - trees with different SHAs,
+- Ti - ignored tree (WD only)
+- S1,S2 - submodules with different SHAs
+- Sd - dirty submodule (WD only)
+- x - nothing
+
+Diff with 2 non-workdir iterators
+---------------------------------
+
+| | Old | New | |
+|----|-----|-----|------------------------------------------------------------|
+| 0 | x | x | nothing |
+| 1 | x | B1 | added blob |
+| 2 | x | T1 | added tree |
+| 3 | B1 | x | removed blob |
+| 4 | B1 | B1 | unmodified blob |
+| 5 | B1 | B2 | modified blob |
+| 6 | B1 | T1 | typechange blob -> tree |
+| 7 | T1 | x | removed tree |
+| 8 | T1 | B1 | typechange tree -> blob |
+| 9 | T1 | T1 | unmodified tree |
+| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
+
+
+Now, let's make the "New" iterator into a working directory iterator, so
+we replace "added" items with either untracked or ignored, like this:
+
+Diff with non-work & workdir iterators
+--------------------------------------
+
+| | Old | New | |
+|----|-----|-----|------------------------------------------------------------|
+| 0 | x | x | nothing |
+| 1 | x | B1 | untracked blob |
+| 2 | x | Bi | ignored file |
+| 3 | x | T1 | untracked tree |
+| 4 | x | Ti | ignored tree |
+| 5 | B1 | x | removed blob |
+| 6 | B1 | B1 | unmodified blob |
+| 7 | B1 | B2 | modified blob |
+| 8 | B1 | T1 | typechange blob -> tree |
+| 9 | B1 | Ti | removed blob AND ignored tree as separate items |
+| 10 | T1 | x | removed tree |
+| 11 | T1 | B1 | typechange tree -> blob |
+| 12 | T1 | Bi | removed tree AND ignored blob as separate items |
+| 13 | T1 | T1 | unmodified tree |
+| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
+
+Note: if there is a corresponding entry in the old tree, then a working
+directory item won't be ignored (i.e. no Bi or Ti for tracked items).
+
+
+Now, expand this to three iterators: a baseline tree, a target tree, and
+an actual working directory tree:
+
+Checkout From 3 Iterators (2 not workdir, 1 workdir)
+----------------------------------------------------
+
+(base == old HEAD; target == what to checkout; actual == working dir)
+
+| |base | target | actual/workdir | |
+|-----|-----|------- |----------------|--------------------------------------------------------------------|
+| 0 | x | x | x | nothing |
+| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) |
+| 2+ | x | B1 | x | add blob (SAFE) |
+| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) |
+| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) |
+| 5+ | x | T1 | x | add tree (SAFE) |
+| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) |
+| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) |
+| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) |
+| 9- | B1 | x | B1 | delete blob (SAFE) |
+| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) |
+| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) |
+| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) |
+| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) |
+| 14 | B1 | B1 | B1 | unmodified file (SAFE) |
+| 15 | B1 | B1 | B2 | locally modified file (DIRTY) |
+| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) |
+| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) |
+| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) |
+| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) |
+| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) |
+| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) |
+| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) |
+| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) |
+| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) |
+| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) |
+| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) |
+| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) |
+| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) |
+| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) |
+| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) |
+| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) |
+| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) |
+| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) |
+| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) |
+| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) |
+| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) |
+| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) |
+| 38+ | x | S1 | x | add submodule (SAFE) |
+| 39 | x | S1 | S1/Sd | independently added submodule (SUBMODULE) |
+| 40* | x | S1 | B1 | add submodule with blob confilct (FORCEABLE) |
+| 41* | x | S1 | T1 | add submodule with tree conflict (FORCEABLE) |
+| 42 | S1 | x | S1/Sd | deleted submodule (SUBMODULE) |
+| 43 | S1 | x | x | independently deleted submodule (SUBMODULE) |
+| 44 | S1 | x | B1 | independently deleted submodule with added blob (SAFE+MISSING) |
+| 45 | S1 | x | T1 | independently deleted submodule with added tree (SAFE+MISSING) |
+| 46 | S1 | S1 | x | locally deleted submodule (SUBMODULE) |
+| 47+ | S1 | S2 | x | update locally deleted submodule (SAFE) |
+| 48 | S1 | S1 | S2 | locally updated submodule commit (SUBMODULE) |
+| 49 | S1 | S2 | S1 | updated submodule commit (SUBMODULE) |
+| 50+ | S1 | B1 | x | add blob with locally deleted submodule (SAFE+MISSING) |
+| 51* | S1 | B1 | S1 | typechange submodule->blob (SAFE) |
+| 52* | S1 | B1 | Sd | typechange dirty submodule->blob (SAFE!?!?) |
+| 53+ | S1 | T1 | x | add tree with locally deleted submodule (SAFE+MISSING) |
+| 54* | S1 | T1 | S1/Sd | typechange submodule->tree (MAYBE SAFE) |
+| 55+ | B1 | S1 | x | add submodule with locally deleted blob (SAFE+MISSING) |
+| 56* | B1 | S1 | B1 | typechange blob->submodule (SAFE) |
+| 57+ | T1 | S1 | x | add submodule with locally deleted tree (SAFE+MISSING) |
+| 58* | T1 | S1 | T1 | typechange tree->submodule (SAFE) |
+
+
+The number is followed by ' ' if no change is needed or '+' if the case
+needs to write to disk or '-' if something must be deleted and '*' if
+there should be a delete followed by an write.
+
+There are four tiers of safe cases:
+
+* SAFE == completely safe to update
+* SAFE+MISSING == safe except the workdir is missing the expect content
+* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
+ content, which is unknown at this point
+* FORCEABLE == conflict unless FORCE is given
+* DIRTY == no conflict but change is not applied unless FORCE
+* SUBMODULE == no conflict and no change is applied unless a deleted
+ submodule dir is empty
+
+Some slightly unusual circumstances:
+
+* 8 - parent dir is only deleted when file is, so parent will be left if
+ empty even though it would be deleted if the file were present
+* 11 - core git does not consider this a conflict but attempts to delete T1
+ and gives "unable to unlink file" error yet does not skip the rest
+ of the operation
+* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
+ dirty (and warning message "D file" is printed), with FORCE, file is
+ restored.
+* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
+ combined, but core git considers this a conflict unless forced.
+* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
+ which are ok on their own, but core git treat this as a conflict.
+ If not forced, this is a conflict. If forced, this actually doesn't
+ have to write anything and leaves the new blob as an untracked file.
+* 32 - This is the only case where the baseline and target values match
+ and yet we will still write to the working directory. In all other
+ cases, if baseline == target, we don't touch the workdir (it is
+ either already right or is "dirty"). However, since this case also
+ implies that a ?/B1/x case will exist as well, it can be skipped.
+* 41 - It's not clear how core git distinguishes this case from 39 (mode?).
+* 52 - Core git makes destructive changes without any warning when the
+ submodule is dirty and the type changes to a blob.
+
+Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
+none of them will require making any updates to the working directory.
--- /dev/null
+Diff is broken into four phases:
+
+1. Building a list of things that have changed. These changes are called
+ deltas (git_diff_delta objects) and are grouped into a git_diff_list.
+2. Applying file similarity measurement for rename and copy detection (and
+ to potentially split files that have changed radically). This step is
+ optional.
+3. Computing the textual diff for each delta. Not all deltas have a
+ meaningful textual diff. For those that do, the textual diff can
+ either be generated on the fly and passed to output callbacks or can be
+ turned into a git_diff_patch object.
+4. Formatting the diff and/or patch into standard text formats (such as
+ patches, raw lists, etc).
+
+In the source code, step 1 is implemented in `src/diff.c`, step 2 in
+`src/diff_tform.c`, step 3 in `src/diff_patch.c`, and step 4 in
+`src/diff_print.c`. Additionally, when it comes to accessing file
+content, everything goes through diff drivers that are implemented in
+`src/diff_driver.c`.
+
+External Objects
+----------------
+
+* `git_diff_options` represents user choices about how a diff should be
+ performed and is passed to most diff generating functions.
+* `git_diff_file` represents an item on one side of a possible delta
+* `git_diff_delta` represents a pair of items that have changed in some
+ way - it contains two `git_diff_file` plus a status and other stuff.
+* `git_diff_list` is a list of deltas along with information about how
+ those particular deltas were found.
+* `git_diff_patch` represents the actual diff between a pair of items. In
+ some cases, a delta may not have a corresponding patch, if the objects
+ are binary, for example. The content of a patch will be a set of hunks
+ and lines.
+* A `hunk` is range of lines described by a `git_diff_range` (i.e. "lines
+ 10-20 in the old file became lines 12-23 in the new"). It will have a
+ header that compactly represents that information, and it will have a
+ number of lines of context surrounding added and deleted lines.
+* A `line` is simple a line of data along with a `git_diff_line_t` value
+ that tells how the data should be interpreted (e.g. context or added).
+
+Internal Objects
+----------------
+
+* `git_diff_file_content` is an internal structure that represents the
+ data on one side of an item to be diffed; it is an augmented
+ `git_diff_file` with more flags and the actual file data.
+
+ * it is created from a repository plus a) a git_diff_file, b) a git_blob,
+ or c) raw data and size
+ * there are three main operations on git_diff_file_content:
+
+ * _initialization_ sets up the data structure and does what it can up to,
+ but not including loading and looking at the actual data
+ * _loading_ loads the data, preprocesses it (i.e. applies filters) and
+ potentially analyzes it (to decide if binary)
+ * _free_ releases loaded data and frees any allocated memory
+
+* The internal structure of a `git_diff_patch` stores the actual diff
+ between a pair of `git_diff_file_content` items
+
+ * it may be "unset" if the items are not diffable
+ * "empty" if the items are the same
+ * otherwise it will consist of a set of hunks each of which covers some
+ number of lines of context, additions and deletions
+ * a patch is created from two git_diff_file_content items
+ * a patch is fully instantiated in three phases:
+
+ * initial creation and initialization
+ * loading of data and preliminary data examination
+ * diffing of data and optional storage of diffs
+ * (TBD) if a patch is asked to store the diffs and the size of the diff
+ is significantly smaller than the raw data of the two sides, then the
+ patch may be flattened using a pool of string data
+
+* `git_diff_output` is an internal structure that represents an output
+ target for a `git_diff_patch`
+ * It consists of file, hunk, and line callbacks, plus a payload
+ * There is a standard flattened output that can be used for plain text output
+ * Typically we use a `git_xdiff_output` which drives the callbacks via the
+ xdiff code taken from core Git.
+
+* `git_diff_driver` is an internal structure that encapsulates the logic
+ for a given type of file
+ * a driver is looked up based on the name and mode of a file.
+ * the driver can then be used to:
+ * determine if a file is binary (by attributes, by git_diff_options
+ settings, or by examining the content)
+ * give you a function pointer that is used to evaluate function context
+ for hunk headers
+ * At some point, the logic for getting a filtered version of file content
+ or calculating the OID of a file may be moved into the driver.
--- /dev/null
+Error reporting in libgit2
+==========================
+
+Libgit2 tries to follow the POSIX style: functions return an `int` value
+with 0 (zero) indicating success and negative values indicating an error.
+There are specific negative error codes for each "expected failure"
+(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
+and a generic error code (-1) for all critical or non-specific failures
+(e.g. running out of memory or system corruption).
+
+When a negative value is returned, an error message is also set. The
+message can be accessed via the `giterr_last` function which will return a
+pointer to a `git_error` structure containing the error message text and
+the class of error (i.e. what part of the library generated the error).
+
+For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
+has two expected failure cases: the SHA is not found at all which returns
+`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
+share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of
+critical failures (such as a packfile being corrupted, a loose object
+having the wrong access permissions, etc.) all of which will return -1.
+When the object lookup is successful, it will return 0.
+
+If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using
+CMake), then the error message will be kept in thread-local storage, so it
+will not be modified by other threads. If threads are not enabled, then
+the error message is in global data.
+
+All of the error return codes, the `git_error` type, the error access
+functions, and the error classes are defined in `include/git2/errors.h`.
+See the documentation there for details on the APIs for accessing,
+clearing, and even setting error codes.
+
+When writing libgit2 code, please be smart and conservative when returning
+error codes. Functions usually have a maximum of two or three "expected
+errors" and in most cases only one. If you feel there are more possible
+expected error scenarios, then the API you are writing may be at too high
+a level for core libgit2.
+
+Example usage
+-------------
+
+When using libgit2, you will typically capture the return value from
+functions using an `int` variable and check to see if it is negative.
+When that happens, you can, if you wish, look at the specific value or
+look at the error message that was generated.
+
+~~~c
+{
+ git_repository *repo;
+ int error = git_repository_open(&repo, "path/to/repo");
+
+ if (error < 0) {
+ fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message);
+ exit(1);
+ }
+
+ ... use `repo` here ...
+
+ git_repository_free(repo); /* void function - no error return code */
+}
+~~~
+
+Some of the error return values do have meaning. Optionally, you can look
+at the specific error values to decide what to do.
+
+~~~c
+{
+ git_repository *repo;
+ const char *path = "path/to/repo";
+ int error = git_repository_open(&repo, path);
+
+ if (error < 0) {
+ if (error == GIT_ENOTFOUND)
+ fprintf(stderr, "Could not find repository at path '%s'\n", path);
+ else
+ fprintf(stderr, "Unable to open repository: %s\n",
+ giterr_last()->message);
+ exit(1);
+ }
+
+ ... happy ...
+}
+~~~
+
+Some of the higher-level language bindings may use a range of information
+from libgit2 to convert error return codes into exceptions, including the
+specific error return codes and even the class of error and the error
+message returned by `giterr_last`, but the full range of that logic is
+beyond the scope of this document.
+
+Example internal implementation
+-------------------------------
+
+Internally, libgit2 detects error scenarios, records error messages, and
+returns error values. Errors from low-level functions are generally
+passed upwards (unless the higher level can either handle the error or
+wants to translate the error into something more meaningful).
+
+~~~c
+int git_repository_open(git_repository **repository, const char *path)
+{
+ /* perform some logic to open the repository */
+ if (p_exists(path) < 0) {
+ giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
+ return GIT_ENOTFOUND;
+ }
+
+ ...
+}
+~~~
+
+The public error API
+--------------------
+
+- `const git_error *giterr_last(void)`: The main function used to look up
+ the last error. This may return NULL if no error has occurred.
+ Otherwise this should return a `git_error` object indicating the class
+ of error and the error message that was generated by the library.
+
+ The last error is stored in thread-local storage when libgit2 is
+ compiled with thread support, so you do not have to worry about another
+ thread overwriting the value. When thread support is off, the last
+ error is a global value.
+
+ _Note_ There are some known bugs in the library where this may return
+ NULL even when an error code was generated. Please report these as
+ bugs, but in the meantime, please code defensively and check for NULL
+ when calling this function.
+
+- `void giterr_clear(void)`: This function clears the last error. The
+ library will call this when an error is generated by low level function
+ and the higher level function handles the error.
+
+ _Note_ There are some known bugs in the library where a low level
+ function's error message is not cleared by higher level code that
+ handles the error and returns zero. Please report these as bugs, but in
+ the meantime, a zero return value from a libgit2 API does not guarantee
+ that `giterr_last()` will return NULL.
+
+- `void giterr_set_str(int error_class, const char *message)`: This
+ function can be used when writing a custom backend module to set the
+ libgit2 error message. See the documentation on this function for its
+ use. Normal usage of libgit2 will probably never need to call this API.
+
+- `void giterr_set_oom(void)`: This is a standard function for reporting
+ an out-of-memory error. It is written in a manner that it doesn't have
+ to allocate any extra memory in order to record the error, so this is
+ the best way to report that scenario.
+
+Deviations from the standard
+----------------------------
+
+There are some public functions that do not return `int` values. There
+are two primary cases:
+
+* `void` return values: If a function has a `void` return, then it will
+ never fail. This primary will be used for object destructors.
+
+* `git_xyz *` return values: These are simple accessor functions where the
+ only meaningful error would typically be looking something up by index
+ and having the index be out of bounds. In those cases, the function
+ will typically return NULL.
+
+* Boolean return values: There are some cases where a function cannot fail
+ and wants to return a boolean value. In those cases, we try to return 1
+ for true and 0 for false. These cases are rare and the return value for
+ the function should probably be an `unsigned int` to denote these cases.
+ If you find an exception, please open an issue and let's fix it.
+
+There are a few other exceptions to these rules here and there in the
+library, but those are extremely rare and should probably be converted
+over to other to more standard patterns for usage. Feel free to open
+issues pointing these out.
+
+There are some known bugs in the library where some functions may return a
+negative value but not set an error message and some other functions may
+return zero (no error) and yet leave an error message set. Please report
+these cases as issues and they will be fixed. In the meanwhile, please
+code defensively, checking that the return value of `giterr_last` is not
+NULL before using it, and not relying on `giterr_last` to return NULL when
+a function returns 0 for success.
+
+The internal error API
+----------------------
+
+- `void giterr_set(int error_class, const char *fmt, ...)`: This is the
+ main internal function for setting an error. It works like `printf` to
+ format the error message. See the notes of `giterr_set_str` for a
+ general description of how error messages are stored (and also about
+ special handling for `error_class` of `GITERR_OS`).
+
+Writing error messages
+----------------------
+
+Here are some guidelines when writing error messages:
+
+- Use proper English, and an impersonal or past tenses: *The given path
+ does not exist*, *Failed to lookup object in ODB*
+
+- Use short, direct and objective messages. **One line, max**. libgit2 is
+ a low level library: think that all the messages reported will be thrown
+ as Ruby or Python exceptions. Think how long are common exception
+ messages in those languages.
+
+- **Do not add redundant information to the error message**, specially
+ information that can be inferred from the context.
+
+ E.g. in `git_repository_open`, do not report a message like "Failed to
+ open repository: path not found". Somebody is calling that
+ function. If it fails, they already know that the repository failed to
+ open!
+
+General guidelines for error reporting
+--------------------------------------
+
+- Libgit2 does not handle programming errors with these
+ functions. Programming errors are `assert`ed, and when their source is
+ internal, fixed as soon as possible. This is C, people.
+
+ Example of programming errors that would **not** be handled: passing
+ NULL to a function that expects a valid pointer; passing a `git_tree`
+ to a function that expects a `git_commit`. All these cases need to be
+ identified with `assert` and fixed asap.
+
+ Example of a runtime error: failing to parse a `git_tree` because it
+ contains invalid data. Failing to open a file because it doesn't exist
+ on disk. These errors are handled, a meaningful error message is set,
+ and an error code is returned.
+
+- In general, *do not* try to overwrite errors internally and *do*
+ propagate error codes from lower level functions to the higher level.
+ There are some cases where propagating an error code will be more
+ confusing rather than less, so there are some exceptions to this rule,
+ but the default behavior should be to simply clean up and pass the error
+ on up to the caller.
+
+ **WRONG**
+
+ ~~~c
+ int git_commit_parent(...)
+ {
+ ...
+
+ if (git_commit_lookup(parent, repo, parent_id) < 0) {
+ giterr_set(GITERR_COMMIT, "Overwrite lookup error message");
+ return -1; /* mask error code */
+ }
+
+ ...
+ }
+ ~~~
+
+ **RIGHT**
+
+ ~~~c
+ int git_commit_parent(...)
+ {
+ ...
+
+ error = git_commit_lookup(parent, repo, parent_id);
+ if (error < 0) {
+ /* cleanup intermediate objects if necessary */
+ /* leave error message and propagate error code */
+ return error;
+ }
+
+ ...
+ }
+ ~~~
--- /dev/null
+Anc / Our / Thr represent the ancestor / ours / theirs side of a merge
+from branch "branch" into HEAD. Workdir represents the expected files in
+the working directory. Index represents the expected files in the index,
+with stage markers.
+
+ Anc Our Thr Workdir Index
+1 D D
+ D/F D/F D/F [0]
+
+2 D D+ D~HEAD (mod/del) D/F [0]
+ D/F D/F D [1]
+ D [2]
+
+3 D D D/F D/F [0]
+ D/F
+
+4 D D+ D~branch (mod/del) D/F [0]
+ D/F D/F D [1]
+ D [3]
+
+5 D D/F (add/add) D/F [2]
+ D/F D/F [3]
+ D/F
+
+6 D/F D/F D D [0]
+ D
+
+7 D/F D/F+ D/F (mod/del) D/F [1]
+ D D~branch (fil/dir) D/F [2]
+ D [3]
+
+8 D/F D/F D D [0]
+ D
+
+9 D/F D/F+ D/F (mod/del) D/F [1]
+ D D~HEAD (fil/dir) D [2]
+ D/F [3]
+
+10 D/F D/F (fil/dir) D/F [0]
+ D D~HEAD D [2]
+ D
--- /dev/null
+# This document lists the authors that have given voice to
+# their decision regarding relicensing the GPL'd code from
+# git.git to the GPL + gcc-exception license used by libgit2.
+#
+# Note that the permission is given for libgit2 use only. For
+# other uses, you must ask each of the contributors yourself.
+#
+# To show the owners of a file in git.git, one can run the
+# following command:
+#
+# git blame -C -C -M -- file | \
+# sed -e 's/[^(]*(\([^0-9]*\).*/\1/' -e 's/[\t ]*$//' | \
+# sort | uniq -c | sort -nr
+#
+# If everyone on the list that produces are on the list in
+# the recently added file "git.git-authors", it *should* be
+# safe to include that code in libgit2, but make sure to
+# read the file to ensure the code doesn't originate from
+# somewhere else.
+#
+# The format of this list is
+# "ok/no/ask/???"<tab>"Author"<space>"<email>"
+#
+# "ok" means the author consents to relicensing all their
+# contributed code (possibly with some exceptions)
+# "no" means the author does not consent
+# "ask" means that the contributor wants to give/withhold
+# his/her consent on a patch-by-patch basis.
+# "???" means the person is a prominent contributor who has
+# not yet made his/her standpoint clear.
+#
+# Please try to keep the list alphabetically ordered. It will
+# help in case we get all 600-ish git.git authors on it.
+#
+# (Paul Kocher is the author of the mozilla-sha1 implementation
+# but has otherwise not contributed to git.)
+#
+ok Adam Simpkins <adam@adamsimpkins.net> (http transport)
+ok Adrian Johnson <ajohnson@redneon.com>
+ok Alexey Shumkin <alex.crezoff@gmail.com>
+ok Andreas Ericsson <ae@op5.se>
+ok Antoine Pelisse <apelisse@gmail.com>
+ok Boyd Lynn Gerber <gerberb@zenez.com>
+ok Brandon Casey <drafnel@gmail.com>
+ok Brian Downing <bdowning@lavos.net>
+ok Brian Gernhardt <benji@silverinsanity.com>
+ok Christian Couder <chriscool@tuxfamily.org>
+ok Daniel Barkalow <barkalow@iabervon.org>
+ok Florian Forster <octo@verplant.org>
+ok Gustaf Hendeby <hendeby@isy.liu.se>
+ok Holger Weiss <holger@zedat.fu-berlin.de>
+ok Jeff King <peff@peff.net>
+ok Johannes Schindelin <Johannes.Schindelin@gmx.de>
+ok Johannes Sixt <j6t@kdbg.org>
+ask Jonathan Nieder <jrnieder@gmail.com>
+ok Junio C Hamano <gitster@pobox.com>
+ok Kristian Høgsberg <krh@redhat.com>
+ok Linus Torvalds <torvalds@linux-foundation.org>
+ok Lukas Sandström <lukass@etek.chalmers.se>
+ok Matthieu Moy <Matthieu.Moy@imag.fr>
+ok Michael Haggerty <mhagger@alum.mit.edu>
+ok Nicolas Pitre <nico@fluxnic.net> <nico@cam.org>
+ok Paolo Bonzini <bonzini@gnu.org>
+ok Paul Kocher <paul@cryptography.com>
+ok Peter Hagervall <hager@cs.umu.se>
+ok Petr Onderka <gsvick@gmail.com>
+ok Pierre Habouzit <madcoder@debian.org>
+ok Pieter de Bie <pdebie@ai.rug.nl>
+ok René Scharfe <rene.scharfe@lsrfire.ath.cx>
+ok Sebastian Schuberth <sschuberth@gmail.com>
+ok Shawn O. Pearce <spearce@spearce.org>
+ok Steffen Prohaska <prohaska@zib.de>
+ok Sven Verdoolaege <skimo@kotnet.org>
+ask Thomas Rast <tr@thomasrast.ch> (ok before 6-Oct-2013)
+ok Torsten Bögershausen <tboegi@web.de>
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_git_h__
+#define INCLUDE_git_git_h__
+
+#include "git2/annotated_commit.h"
+#include "git2/attr.h"
+#include "git2/blob.h"
+#include "git2/blame.h"
+#include "git2/branch.h"
+#include "git2/buffer.h"
+#include "git2/checkout.h"
+#include "git2/cherrypick.h"
+#include "git2/clone.h"
+#include "git2/commit.h"
+#include "git2/common.h"
+#include "git2/config.h"
+#include "git2/describe.h"
+#include "git2/diff.h"
+#include "git2/errors.h"
+#include "git2/filter.h"
+#include "git2/global.h"
+#include "git2/graph.h"
+#include "git2/ignore.h"
+#include "git2/index.h"
+#include "git2/indexer.h"
+#include "git2/merge.h"
+#include "git2/message.h"
+#include "git2/net.h"
+#include "git2/notes.h"
+#include "git2/object.h"
+#include "git2/odb.h"
+#include "git2/odb_backend.h"
+#include "git2/oid.h"
+#include "git2/pack.h"
+#include "git2/patch.h"
+#include "git2/pathspec.h"
+#include "git2/proxy.h"
+#include "git2/rebase.h"
+#include "git2/refdb.h"
+#include "git2/reflog.h"
+#include "git2/refs.h"
+#include "git2/refspec.h"
+#include "git2/remote.h"
+#include "git2/repository.h"
+#include "git2/reset.h"
+#include "git2/revert.h"
+#include "git2/revparse.h"
+#include "git2/revwalk.h"
+#include "git2/signature.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/submodule.h"
+#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/transaction.h"
+#include "git2/tree.h"
+#include "git2/types.h"
+#include "git2/version.h"
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_annotated_commit_h__
+#define INCLUDE_git_annotated_commit_h__
+
+#include "common.h"
+#include "repository.h"
+#include "types.h"
+
+/**
+ * @file git2/annotated_commit.h
+ * @brief Git annotated commit routines
+ * @defgroup git_annotated_commit Git annotated commit routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Creates a `git_annotated_commit` from the given reference.
+ * The resulting git_annotated_commit must be freed with
+ * `git_annotated_commit_free`.
+ *
+ * @param out pointer to store the git_annotated_commit result in
+ * @param repo repository that contains the given reference
+ * @param ref reference to use to lookup the git_annotated_commit
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_annotated_commit_from_ref(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_reference *ref);
+
+/**
+ * Creates a `git_annotated_commit` from the given fetch head data.
+ * The resulting git_annotated_commit must be freed with
+ * `git_annotated_commit_free`.
+ *
+ * @param out pointer to store the git_annotated_commit result in
+ * @param repo repository that contains the given commit
+ * @param branch_name name of the (remote) branch
+ * @param remote_url url of the remote
+ * @param id the commit object id of the remote branch
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_annotated_commit_from_fetchhead(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_url,
+ const git_oid *id);
+
+/**
+ * Creates a `git_annotated_commit` from the given commit id.
+ * The resulting git_annotated_commit must be freed with
+ * `git_annotated_commit_free`.
+ *
+ * An annotated commit contains information about how it was
+ * looked up, which may be useful for functions like merge or
+ * rebase to provide context to the operation. For example,
+ * conflict files will include the name of the source or target
+ * branches being merged. It is therefore preferable to use the
+ * most specific function (eg `git_annotated_commit_from_ref`)
+ * instead of this one when that data is known.
+ *
+ * @param out pointer to store the git_annotated_commit result in
+ * @param repo repository that contains the given commit
+ * @param id the commit object id to lookup
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_annotated_commit_lookup(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_oid *id);
+
+/**
+ * Creates a `git_annotated_comit` from a revision string.
+ *
+ * See `man gitrevisions`, or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * @param out pointer to store the git_annotated_commit result in
+ * @param repo repository that contains the given commit
+ * @param revspec the extended sha syntax string to use to lookup the commit
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_annotated_commit_from_revspec(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const char *revspec);
+
+/**
+ * Gets the commit ID that the given `git_annotated_commit` refers to.
+ *
+ * @param commit the given annotated commit
+ * @return commit id
+ */
+GIT_EXTERN(const git_oid *) git_annotated_commit_id(
+ const git_annotated_commit *commit);
+
+/**
+ * Frees a `git_annotated_commit`.
+ *
+ * @param commit annotated commit to free
+ */
+GIT_EXTERN(void) git_annotated_commit_free(
+ git_annotated_commit *commit);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_attr_h__
+#define INCLUDE_git_attr_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/attr.h
+ * @brief Git attribute management routines
+ * @defgroup git_attr Git attribute management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * GIT_ATTR_TRUE checks if an attribute is set on. In core git
+ * parlance, this the value for "Set" attributes.
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.c foo
+ *
+ * Then for file `xyz.c` looking up attribute "foo" gives a value for
+ * which `GIT_ATTR_TRUE(value)` is true.
+ */
+#define GIT_ATTR_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_TRUE_T)
+
+/**
+ * GIT_ATTR_FALSE checks if an attribute is set off. In core git
+ * parlance, this is the value for attributes that are "Unset" (not to
+ * be confused with values that a "Unspecified").
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.h -foo
+ *
+ * Then for file `zyx.h` looking up attribute "foo" gives a value for
+ * which `GIT_ATTR_FALSE(value)` is true.
+ */
+#define GIT_ATTR_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_FALSE_T)
+
+/**
+ * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This
+ * may be due to the attribute not being mentioned at all or because
+ * the attribute was explicitly set unspecified via the `!` operator.
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.c foo
+ * *.h -foo
+ * onefile.c !foo
+ *
+ * Then for `onefile.c` looking up attribute "foo" yields a value with
+ * `GIT_ATTR_UNSPECIFIED(value)` of true. Also, looking up "foo" on
+ * file `onefile.rb` or looking up "bar" on any file will all give
+ * `GIT_ATTR_UNSPECIFIED(value)` of true.
+ */
+#define GIT_ATTR_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_UNSPECIFIED_T)
+
+/**
+ * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as
+ * opposed to TRUE, FALSE or UNSPECIFIED). This would be the case if
+ * for a file with something like:
+ *
+ * *.txt eol=lf
+ *
+ * Given this, looking up "eol" for `onefile.txt` will give back the
+ * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true.
+ */
+#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_T)
+
+/**
+ * Possible states for an attribute
+ */
+typedef enum {
+ GIT_ATTR_UNSPECIFIED_T = 0, /**< The attribute has been left unspecified */
+ GIT_ATTR_TRUE_T, /**< The attribute has been set */
+ GIT_ATTR_FALSE_T, /**< The attribute has been unset */
+ GIT_ATTR_VALUE_T, /**< This attribute has a value */
+} git_attr_t;
+
+/**
+ * Return the value type for a given attribute.
+ *
+ * This can be either `TRUE`, `FALSE`, `UNSPECIFIED` (if the attribute
+ * was not set at all), or `VALUE`, if the attribute was set to an
+ * actual string.
+ *
+ * If the attribute has a `VALUE` string, it can be accessed normally
+ * as a NULL-terminated C string.
+ *
+ * @param attr The attribute
+ * @return the value type for the attribute
+ */
+GIT_EXTERN(git_attr_t) git_attr_value(const char *attr);
+
+/**
+ * Check attribute flags: Reading values from index and working directory.
+ *
+ * When checking attributes, it is possible to check attribute files
+ * in both the working directory (if there is one) and the index (if
+ * there is one). You can explicitly choose where to check and in
+ * which order using the following flags.
+ *
+ * Core git usually checks the working directory then the index,
+ * except during a checkout when it checks the index first. It will
+ * use index only for creating archives or for a bare repo (if an
+ * index has been specified for the bare repo).
+ */
+#define GIT_ATTR_CHECK_FILE_THEN_INDEX 0
+#define GIT_ATTR_CHECK_INDEX_THEN_FILE 1
+#define GIT_ATTR_CHECK_INDEX_ONLY 2
+
+/**
+ * Check attribute flags: Using the system attributes file.
+ *
+ * Normally, attribute checks include looking in the /etc (or system
+ * equivalent) directory for a `gitattributes` file. Passing this
+ * flag will cause attribute checks to ignore that file.
+ */
+#define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2)
+
+/**
+ * Look up the value of one git attribute for path.
+ *
+ * @param value_out Output of the value of the attribute. Use the GIT_ATTR_...
+ * macros to test for TRUE, FALSE, UNSPECIFIED, etc. or just
+ * use the string value for attributes set to a value. You
+ * should NOT modify or free this value.
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path The path to check for attributes. Relative paths are
+ * interpreted relative to the repo root. The file does
+ * not have to exist, but if it does not, then it will be
+ * treated as a plain file (not a directory).
+ * @param name The name of the attribute to look up.
+ */
+GIT_EXTERN(int) git_attr_get(
+ const char **value_out,
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ const char *name);
+
+/**
+ * Look up a list of git attributes for path.
+ *
+ * Use this if you have a known list of attributes that you want to
+ * look up in a single call. This is somewhat more efficient than
+ * calling `git_attr_get()` multiple times.
+ *
+ * For example, you might write:
+ *
+ * const char *attrs[] = { "crlf", "diff", "foo" };
+ * const char **values[3];
+ * git_attr_get_many(values, repo, 0, "my/fun/file.c", 3, attrs);
+ *
+ * Then you could loop through the 3 values to get the settings for
+ * the three attributes you asked about.
+ *
+ * @param values_out An array of num_attr entries that will have string
+ * pointers written into it for the values of the attributes.
+ * You should not modify or free the values that are written
+ * into this array (although of course, you should free the
+ * array itself if you allocated it).
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path The path inside the repo to check attributes. This
+ * does not have to exist, but if it does not, then
+ * it will be treated as a plain file (i.e. not a directory).
+ * @param num_attr The number of attributes being looked up
+ * @param names An array of num_attr strings containing attribute names.
+ */
+GIT_EXTERN(int) git_attr_get_many(
+ const char **values_out,
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ size_t num_attr,
+ const char **names);
+
+typedef int (*git_attr_foreach_cb)(const char *name, const char *value, void *payload);
+
+/**
+ * Loop over all the git attributes for a path.
+ *
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path Path inside the repo to check attributes. This does not have
+ * to exist, but if it does not, then it will be treated as a
+ * plain file (i.e. not a directory).
+ * @param callback Function to invoke on each attribute name and value. The
+ * value may be NULL is the attribute is explicitly set to
+ * UNSPECIFIED using the '!' sign. Callback will be invoked
+ * only once per attribute name, even if there are multiple
+ * rules for a given file. The highest priority rule will be
+ * used. Return a non-zero value from this to stop looping.
+ * The value will be returned from `git_attr_foreach`.
+ * @param payload Passed on as extra parameter to callback function.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_attr_foreach(
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ git_attr_foreach_cb callback,
+ void *payload);
+
+/**
+ * Flush the gitattributes cache.
+ *
+ * Call this if you have reason to believe that the attributes files on
+ * disk no longer match the cached contents of memory. This will cause
+ * the attributes files to be reloaded the next time that an attribute
+ * access function is called.
+ */
+GIT_EXTERN(void) git_attr_cache_flush(
+ git_repository *repo);
+
+/**
+ * Add a macro definition.
+ *
+ * Macros will automatically be loaded from the top level `.gitattributes`
+ * file of the repository (plus the build-in "binary" macro). This
+ * function allows you to add others. For example, to add the default
+ * macro, you would call:
+ *
+ * git_attr_add_macro(repo, "binary", "-diff -crlf");
+ */
+GIT_EXTERN(int) git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values);
+
+/** @} */
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_blame_h__
+#define INCLUDE_git_blame_h__
+
+#include "common.h"
+#include "oid.h"
+
+/**
+ * @file git2/blame.h
+ * @brief Git blame routines
+ * @defgroup git_blame Git blame routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Flags for indicating option behavior for git_blame APIs.
+ */
+typedef enum {
+ /** Normal blame, the default */
+ GIT_BLAME_NORMAL = 0,
+ /** Track lines that have moved within a file (like `git blame -M`).
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0),
+ /** Track lines that have moved across files in the same commit (like `git blame -C`).
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1),
+ /** Track lines that have been copied from another file that exists in the
+ * same commit (like `git blame -CC`). Implies SAME_FILE.
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2),
+ /** Track lines that have been copied from another file that exists in *any*
+ * commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES.
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3),
+ /** Restrict the search of commits to those reachable following only the
+ * first parents. */
+ GIT_BLAME_FIRST_PARENT = (1<<4),
+} git_blame_flag_t;
+
+/**
+ * Blame options structure
+ *
+ * Use zeros to indicate default settings. It's easiest to use the
+ * `GIT_BLAME_OPTIONS_INIT` macro:
+ * git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+ *
+ * - `flags` is a combination of the `git_blame_flag_t` values above.
+ * - `min_match_characters` is the lower bound on the number of alphanumeric
+ * characters that must be detected as moving/copying within a file for it to
+ * associate those lines with the parent commit. The default value is 20.
+ * This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
+ * flags are specified.
+ * - `newest_commit` is the id of the newest commit to consider. The default
+ * is HEAD.
+ * - `oldest_commit` is the id of the oldest commit to consider. The default
+ * is the first commit encountered with a NULL parent.
+ * - `min_line` is the first line in the file to blame. The default is 1 (line
+ * numbers start with 1).
+ * - `max_line` is the last line in the file to blame. The default is the last
+ * line of the file.
+ */
+typedef struct git_blame_options {
+ unsigned int version;
+
+ uint32_t flags;
+ uint16_t min_match_characters;
+ git_oid newest_commit;
+ git_oid oldest_commit;
+ size_t min_line;
+ size_t max_line;
+} git_blame_options;
+
+#define GIT_BLAME_OPTIONS_VERSION 1
+#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_blame_options` with default values. Equivalent to
+ * creating an instance with GIT_BLAME_OPTIONS_INIT.
+ *
+ * @param opts The `git_blame_options` struct to initialize
+ * @param version Version of struct; pass `GIT_BLAME_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_blame_init_options(
+ git_blame_options *opts,
+ unsigned int version);
+
+/**
+ * Structure that represents a blame hunk.
+ *
+ * - `lines_in_hunk` is the number of lines in this hunk
+ * - `final_commit_id` is the OID of the commit where this line was last
+ * changed.
+ * - `final_start_line_number` is the 1-based line number where this hunk
+ * begins, in the final version of the file
+ * - `orig_commit_id` is the OID of the commit where this hunk was found. This
+ * will usually be the same as `final_commit_id`, except when
+ * `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified.
+ * - `orig_path` is the path to the file where this hunk originated, as of the
+ * commit specified by `orig_commit_id`.
+ * - `orig_start_line_number` is the 1-based line number where this hunk begins
+ * in the file named by `orig_path` in the commit specified by
+ * `orig_commit_id`.
+ * - `boundary` is 1 iff the hunk has been tracked to a boundary commit (the
+ * root, or the commit specified in git_blame_options.oldest_commit)
+ */
+typedef struct git_blame_hunk {
+ size_t lines_in_hunk;
+
+ git_oid final_commit_id;
+ size_t final_start_line_number;
+ git_signature *final_signature;
+
+ git_oid orig_commit_id;
+ const char *orig_path;
+ size_t orig_start_line_number;
+ git_signature *orig_signature;
+
+ char boundary;
+} git_blame_hunk;
+
+
+/* Opaque structure to hold blame results */
+typedef struct git_blame git_blame;
+
+/**
+ * Gets the number of hunks that exist in the blame structure.
+ */
+GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);
+
+/**
+ * Gets the blame hunk at the given index.
+ *
+ * @param blame the blame structure to query
+ * @param index index of the hunk to retrieve
+ * @return the hunk at the given index, or NULL on error
+ */
+GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex(
+ git_blame *blame,
+ uint32_t index);
+
+/**
+ * Gets the hunk that relates to the given line number in the newest commit.
+ *
+ * @param blame the blame structure to query
+ * @param lineno the (1-based) line number to find a hunk for
+ * @return the hunk that contains the given line, or NULL on error
+ */
+GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline(
+ git_blame *blame,
+ size_t lineno);
+
+/**
+ * Get the blame for a single file.
+ *
+ * @param out pointer that will receive the blame object
+ * @param repo repository whose history is to be walked
+ * @param path path to file to consider
+ * @param options options for the blame operation. If NULL, this is treated as
+ * though GIT_BLAME_OPTIONS_INIT were passed.
+ * @return 0 on success, or an error code. (use giterr_last for information
+ * about the error.)
+ */
+GIT_EXTERN(int) git_blame_file(
+ git_blame **out,
+ git_repository *repo,
+ const char *path,
+ git_blame_options *options);
+
+
+/**
+ * Get blame data for a file that has been modified in memory. The `reference`
+ * parameter is a pre-calculated blame for the in-odb history of the file. This
+ * means that once a file blame is completed (which can be expensive), updating
+ * the buffer blame is very fast.
+ *
+ * Lines that differ between the buffer and the committed version are marked as
+ * having a zero OID for their final_commit_id.
+ *
+ * @param out pointer that will receive the resulting blame data
+ * @param reference cached blame from the history of the file (usually the output
+ * from git_blame_file)
+ * @param buffer the (possibly) modified contents of the file
+ * @param buffer_len number of valid bytes in the buffer
+ * @return 0 on success, or an error code. (use giterr_last for information
+ * about the error)
+ */
+GIT_EXTERN(int) git_blame_buffer(
+ git_blame **out,
+ git_blame *reference,
+ const char *buffer,
+ size_t buffer_len);
+
+/**
+ * Free memory allocated by git_blame_file or git_blame_buffer.
+ *
+ * @param blame the blame structure to free
+ */
+GIT_EXTERN(void) git_blame_free(git_blame *blame);
+
+/** @} */
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_blob_h__
+#define INCLUDE_git_blob_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "object.h"
+#include "buffer.h"
+
+/**
+ * @file git2/blob.h
+ * @brief Git blob load and write routines
+ * @defgroup git_blob Git blob load and write routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a blob object from a repository.
+ *
+ * @param blob pointer to the looked up blob
+ * @param repo the repo to use when locating the blob.
+ * @param id identity of the blob to locate.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git_oid *id);
+
+/**
+ * Lookup a blob object from a repository,
+ * given a prefix of its identifier (short id).
+ *
+ * @see git_object_lookup_prefix
+ *
+ * @param blob pointer to the looked up blob
+ * @param repo the repo to use when locating the blob.
+ * @param id identity of the blob to locate.
+ * @param len the length of the short identifier
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, size_t len);
+
+/**
+ * Close an open blob
+ *
+ * This is a wrapper around git_object_free()
+ *
+ * IMPORTANT:
+ * It *is* necessary to call this method when you stop
+ * using a blob. Failure to do so will cause a memory leak.
+ *
+ * @param blob the blob to close
+ */
+GIT_EXTERN(void) git_blob_free(git_blob *blob);
+
+/**
+ * Get the id of a blob.
+ *
+ * @param blob a previously loaded blob.
+ * @return SHA1 hash for this blob.
+ */
+GIT_EXTERN(const git_oid *) git_blob_id(const git_blob *blob);
+
+/**
+ * Get the repository that contains the blob.
+ *
+ * @param blob A previously loaded blob.
+ * @return Repository that contains this blob.
+ */
+GIT_EXTERN(git_repository *) git_blob_owner(const git_blob *blob);
+
+/**
+ * Get a read-only buffer with the raw content of a blob.
+ *
+ * A pointer to the raw content of a blob is returned;
+ * this pointer is owned internally by the object and shall
+ * not be free'd. The pointer may be invalidated at a later
+ * time.
+ *
+ * @param blob pointer to the blob
+ * @return the pointer
+ */
+GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob);
+
+/**
+ * Get the size in bytes of the contents of a blob
+ *
+ * @param blob pointer to the blob
+ * @return size on bytes
+ */
+GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob);
+
+/**
+ * Get a buffer with the filtered content of a blob.
+ *
+ * This applies filters as if the blob was being checked out to the
+ * working directory under the specified filename. This may apply
+ * CRLF filtering or other types of changes depending on the file
+ * attributes set for the blob and the content detected in it.
+ *
+ * The output is written into a `git_buf` which the caller must free
+ * when done (via `git_buf_free`).
+ *
+ * If no filters need to be applied, then the `out` buffer will just
+ * be populated with a pointer to the raw content of the blob. In
+ * that case, be careful to *not* free the blob until done with the
+ * buffer or copy it into memory you own.
+ *
+ * @param out The git_buf to be filled in
+ * @param blob Pointer to the blob
+ * @param as_path Path used for file attribute lookups, etc.
+ * @param check_for_binary_data Should this test if blob content contains
+ * NUL bytes / looks like binary data before applying filters?
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_blob_filtered_content(
+ git_buf *out,
+ git_blob *blob,
+ const char *as_path,
+ int check_for_binary_data);
+
+/**
+ * Read a file from the working folder of a repository
+ * and write it to the Object Database as a loose blob
+ *
+ * @param id return the id of the written blob
+ * @param repo repository where the blob will be written.
+ * this repository cannot be bare
+ * @param relative_path file from which the blob will be created,
+ * relative to the repository's working dir
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path);
+
+/**
+ * Read a file from the filesystem and write its content
+ * to the Object Database as a loose blob
+ *
+ * @param id return the id of the written blob
+ * @param repo repository where the blob will be written.
+ * this repository can be bare or not
+ * @param path file from which the blob will be created
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path);
+
+/**
+ * Create a stream to write a new blob into the object db
+ *
+ * This function may need to buffer the data on disk and will in
+ * general not be the right choice if you know the size of the data
+ * to write. If you have data in memory, use
+ * `git_blob_create_frombuffer()`. If you do not, but know the size of
+ * the contents (and don't want/need to perform filtering), use
+ * `git_odb_open_wstream()`.
+ *
+ * Don't close this stream yourself but pass it to
+ * `git_blob_create_fromstream_commit()` to commit the write to the
+ * object db and get the object id.
+ *
+ * If the `hintpath` parameter is filled, it will be used to determine
+ * what git filters should be applied to the object before it is written
+ * to the object database.
+ *
+ * @param out the stream into which to write
+ * @param repo Repository where the blob will be written.
+ * This repository can be bare or not.
+ * @param hintpath If not NULL, will be used to select data filters
+ * to apply onto the content of the blob to be created.
+ * @return 0 or error code
+ */
+GIT_EXTERN(int) git_blob_create_fromstream(
+ git_writestream **out,
+ git_repository *repo,
+ const char *hintpath);
+
+/**
+ * Close the stream and write the blob to the object db
+ *
+ * The stream will be closed and freed.
+ *
+ * @param out the id of the new blob
+ * @param stream the stream to close
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_fromstream_commit(
+ git_oid *out,
+ git_writestream *stream);
+
+/**
+ * Write an in-memory buffer to the ODB as a blob
+ *
+ * @param id return the id of the written blob
+ * @param repo repository where to blob will be written
+ * @param buffer data to be written into the blob
+ * @param len length of the data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_frombuffer(
+ git_oid *id, git_repository *repo, const void *buffer, size_t len);
+
+/**
+ * Determine if the blob content is most certainly binary or not.
+ *
+ * The heuristic used to guess if a file is binary is taken from core git:
+ * Searching for NUL bytes and looking for a reasonable ratio of printable
+ * to non-printable characters among the first 8000 bytes.
+ *
+ * @param blob The blob which content should be analyzed
+ * @return 1 if the content of the blob is detected
+ * as binary; 0 otherwise.
+ */
+GIT_EXTERN(int) git_blob_is_binary(const git_blob *blob);
+
+/**
+ * Create an in-memory copy of a blob. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the object
+ * @param source Original object to copy
+ */
+GIT_EXTERN(int) git_blob_dup(git_blob **out, git_blob *source);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_branch_h__
+#define INCLUDE_git_branch_h__
+
+#include "common.h"
+#include "oid.h"
+#include "types.h"
+
+/**
+ * @file git2/branch.h
+ * @brief Git branch parsing routines
+ * @defgroup git_branch Git branch management
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new branch pointing at a target commit
+ *
+ * A new direct reference will be created pointing to
+ * this target commit. If `force` is true and a reference
+ * already exists with the given name, it'll be replaced.
+ *
+ * The returned reference must be freed by the user.
+ *
+ * The branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out Pointer where to store the underlying reference.
+ *
+ * @param branch_name Name for the branch; this name is
+ * validated for consistency. It should also not conflict with
+ * an already existing branch name.
+ *
+ * @param target Commit to which this branch should point. This object
+ * must belong to the given `repo`.
+ *
+ * @param force Overwrite existing branch.
+ *
+ * @return 0, GIT_EINVALIDSPEC or an error code.
+ * A proper reference is written in the refs/heads namespace
+ * pointing to the provided target commit.
+ */
+GIT_EXTERN(int) git_branch_create(
+ git_reference **out,
+ git_repository *repo,
+ const char *branch_name,
+ const git_commit *target,
+ int force);
+
+/**
+ * Create a new branch pointing at a target commit
+ *
+ * This behaves like `git_branch_create()` but takes an annotated
+ * commit, which lets you specify which extended sha syntax string was
+ * specified by a user, allowing for more exact reflog messages.
+ *
+ * See the documentation for `git_branch_create()`.
+ *
+ * @see git_branch_create
+ */
+GIT_EXTERN(int) git_branch_create_from_annotated(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_annotated_commit *commit,
+ int force);
+
+/**
+ * Delete an existing branch reference.
+ *
+ * If the branch is successfully deleted, the passed reference
+ * object will be invalidated. The reference must be freed manually
+ * by the user.
+ *
+ * @param branch A valid reference representing a branch
+ * @return 0 on success, or an error code.
+ */
+GIT_EXTERN(int) git_branch_delete(git_reference *branch);
+
+/** Iterator type for branches */
+typedef struct git_branch_iterator git_branch_iterator;
+
+/**
+ * Create an iterator which loops over the requested branches.
+ *
+ * @param out the iterator
+ * @param repo Repository where to find the branches.
+ * @param list_flags Filtering flags for the branch
+ * listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE
+ * or GIT_BRANCH_ALL.
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_branch_iterator_new(
+ git_branch_iterator **out,
+ git_repository *repo,
+ git_branch_t list_flags);
+
+/**
+ * Retrieve the next branch from the iterator
+ *
+ * @param out the reference
+ * @param out_type the type of branch (local or remote-tracking)
+ * @param iter the branch iterator
+ * @return 0 on success, GIT_ITEROVER if there are no more branches or an error code.
+ */
+GIT_EXTERN(int) git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *iter);
+
+/**
+ * Free a branch iterator
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter);
+
+/**
+ * Move/rename an existing local branch reference.
+ *
+ * The new branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @param new_branch_name Target name of the branch once the move
+ * is performed; this name is validated for consistency.
+ *
+ * @param force Overwrite existing branch.
+ *
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force);
+
+/**
+ * Lookup a branch by its name in a repository.
+ *
+ * The generated reference must be freed by the user.
+ *
+ * The branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out pointer to the looked-up branch reference
+ *
+ * @param repo the repository to look up the branch
+ *
+ * @param branch_name Name of the branch to be looked-up;
+ * this name is validated for consistency.
+ *
+ * @param branch_type Type of the considered branch. This should
+ * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.
+ *
+ * @return 0 on success; GIT_ENOTFOUND when no matching branch
+ * exists, GIT_EINVALIDSPEC, otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_lookup(
+ git_reference **out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type);
+
+/**
+ * Return the name of the given local or remote branch.
+ *
+ * The name of the branch matches the definition of the name
+ * for git_branch_lookup. That is, if the returned name is given
+ * to git_branch_lookup() then the reference is returned that
+ * was given to this function.
+ *
+ * @param out where the pointer of branch name is stored;
+ * this is valid as long as the ref is not freed.
+ * @param ref the reference ideally pointing to a branch
+ *
+ * @return 0 on success; otherwise an error code (e.g., if the
+ * ref is no local or remote branch).
+ */
+GIT_EXTERN(int) git_branch_name(
+ const char **out,
+ const git_reference *ref);
+
+/**
+ * Return the reference supporting the remote tracking branch,
+ * given a local branch reference.
+ *
+ * @param out Pointer where to store the retrieved
+ * reference.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @return 0 on success; GIT_ENOTFOUND when no remote tracking
+ * reference exists, otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_upstream(
+ git_reference **out,
+ const git_reference *branch);
+
+/**
+ * Set the upstream configuration for a given local branch
+ *
+ * @param branch the branch to configure
+ *
+ * @param upstream_name remote-tracking or local branch to set as
+ * upstream. Pass NULL to unset.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_branch_set_upstream(git_reference *branch, const char *upstream_name);
+
+/**
+ * Return the name of the reference supporting the remote tracking branch,
+ * given the name of a local branch reference.
+ *
+ * @param out Pointer to the user-allocated git_buf which will be
+ * filled with the name of the reference.
+ *
+ * @param repo the repository where the branches live
+ *
+ * @param refname reference name of the local branch.
+ *
+ * @return 0, GIT_ENOTFOUND when no remote tracking reference exists,
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_upstream_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname);
+
+/**
+ * Determine if the current local branch is pointed at by HEAD.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @return 1 if HEAD points at the branch, 0 if it isn't,
+ * error code otherwise.
+ */
+GIT_EXTERN(int) git_branch_is_head(
+ const git_reference *branch);
+
+/**
+ * Return the name of remote that the remote tracking branch belongs to.
+ *
+ * @param out Pointer to the user-allocated git_buf which will be filled with the name of the remote.
+ *
+ * @param repo The repository where the branch lives.
+ *
+ * @param canonical_branch_name name of the remote tracking branch.
+ *
+ * @return 0, GIT_ENOTFOUND
+ * when no remote matching remote was found,
+ * GIT_EAMBIGUOUS when the branch maps to several remotes,
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_remote_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *canonical_branch_name);
+
+
+/**
+ * Retrieve the name fo the upstream remote of a local branch
+ *
+ * @param buf the buffer into which to write the name
+ * @param repo the repository in which to look
+ * @param refname the full name of the branch
+ * @return 0 or an error code
+ */
+ GIT_EXTERN(int) git_branch_upstream_remote(git_buf *buf, git_repository *repo, const char *refname);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_buf_h__
+#define INCLUDE_git_buf_h__
+
+#include "common.h"
+
+/**
+ * @file git2/buffer.h
+ * @brief Buffer export structure
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * A data buffer for exporting data from libgit2
+ *
+ * Sometimes libgit2 wants to return an allocated data buffer to the
+ * caller and have the caller take responsibility for freeing that memory.
+ * This can be awkward if the caller does not have easy access to the same
+ * allocation functions that libgit2 is using. In those cases, libgit2
+ * will fill in a `git_buf` and the caller can use `git_buf_free()` to
+ * release it when they are done.
+ *
+ * A `git_buf` may also be used for the caller to pass in a reference to
+ * a block of memory they hold. In this case, libgit2 will not resize or
+ * free the memory, but will read from it as needed.
+ *
+ * A `git_buf` is a public structure with three fields:
+ *
+ * - `ptr` points to the start of the allocated memory. If it is NULL,
+ * then the `git_buf` is considered empty and libgit2 will feel free
+ * to overwrite it with new data.
+ *
+ * - `size` holds the size (in bytes) of the data that is actually used.
+ *
+ * - `asize` holds the known total amount of allocated memory if the `ptr`
+ * was allocated by libgit2. It may be larger than `size`. If `ptr`
+ * was not allocated by libgit2 and should not be resized and/or freed,
+ * then `asize` will be set to zero.
+ *
+ * Some APIs may occasionally do something slightly unusual with a buffer,
+ * such as setting `ptr` to a value that was passed in by the user. In
+ * those cases, the behavior will be clearly documented by the API.
+ */
+typedef struct {
+ char *ptr;
+ size_t asize, size;
+} git_buf;
+
+/**
+ * Static initializer for git_buf from static buffer
+ */
+#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) }
+
+/**
+ * Free the memory referred to by the git_buf.
+ *
+ * Note that this does not free the `git_buf` itself, just the memory
+ * pointed to by `buffer->ptr`. This will not free the memory if it looks
+ * like it was not allocated internally, but it will clear the buffer back
+ * to the empty state.
+ *
+ * @param buffer The buffer to deallocate
+ */
+GIT_EXTERN(void) git_buf_free(git_buf *buffer);
+
+/**
+ * Resize the buffer allocation to make more space.
+ *
+ * This will attempt to grow the buffer to accommodate the target size.
+ *
+ * If the buffer refers to memory that was not allocated by libgit2 (i.e.
+ * the `asize` field is zero), then `ptr` will be replaced with a newly
+ * allocated block of data. Be careful so that memory allocated by the
+ * caller is not lost. As a special variant, if you pass `target_size` as
+ * 0 and the memory is not allocated by libgit2, this will allocate a new
+ * buffer of size `size` and copy the external data into it.
+ *
+ * Currently, this will never shrink a buffer, only expand it.
+ *
+ * If the allocation fails, this will return an error and the buffer will be
+ * marked as invalid for future operations, invaliding the contents.
+ *
+ * @param buffer The buffer to be resized; may or may not be allocated yet
+ * @param target_size The desired available size
+ * @return 0 on success, -1 on allocation failure
+ */
+GIT_EXTERN(int) git_buf_grow(git_buf *buffer, size_t target_size);
+
+/**
+ * Set buffer to a copy of some raw data.
+ *
+ * @param buffer The buffer to set
+ * @param data The data to copy into the buffer
+ * @param datalen The length of the data to copy into the buffer
+ * @return 0 on success, -1 on allocation failure
+ */
+GIT_EXTERN(int) git_buf_set(
+ git_buf *buffer, const void *data, size_t datalen);
+
+/**
+* Check quickly if buffer looks like it contains binary data
+*
+* @param buf Buffer to check
+* @return 1 if buffer looks like non-text data
+*/
+GIT_EXTERN(int) git_buf_is_binary(const git_buf *buf);
+
+/**
+* Check quickly if buffer contains a NUL byte
+*
+* @param buf Buffer to check
+* @return 1 if buffer contains a NUL byte
+*/
+GIT_EXTERN(int) git_buf_contains_nul(const git_buf *buf);
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_checkout_h__
+#define INCLUDE_git_checkout_h__
+
+#include "common.h"
+#include "types.h"
+#include "diff.h"
+
+/**
+ * @file git2/checkout.h
+ * @brief Git checkout routines
+ * @defgroup git_checkout Git checkout routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Checkout behavior flags
+ *
+ * In libgit2, checkout is used to update the working directory and index
+ * to match a target tree. Unlike git checkout, it does not move the HEAD
+ * commit for you - use `git_repository_set_head` or the like to do that.
+ *
+ * Checkout looks at (up to) four things: the "target" tree you want to
+ * check out, the "baseline" tree of what was checked out previously, the
+ * working directory for actual files, and the index for staged changes.
+ *
+ * You give checkout one of three strategies for update:
+ *
+ * - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
+ * etc., but doesn't make any actual changes.
+ *
+ * - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to
+ * make the working directory match the target (including potentially
+ * discarding modified files).
+ *
+ * - `GIT_CHECKOUT_SAFE` is between these two options, it will only make
+ * modifications that will not lose changes.
+ *
+ * | target == baseline | target != baseline |
+ * ---------------------|-----------------------|----------------------|
+ * workdir == baseline | no action | create, update, or |
+ * | | delete file |
+ * ---------------------|-----------------------|----------------------|
+ * workdir exists and | no action | conflict (notify |
+ * is != baseline | notify dirty MODIFIED | and cancel checkout) |
+ * ---------------------|-----------------------|----------------------|
+ * workdir missing, | notify dirty DELETED | create file |
+ * baseline present | | |
+ * ---------------------|-----------------------|----------------------|
+ *
+ * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
+ * notification callback (see below) that displays information about dirty
+ * files. The default behavior will cancel checkout on conflicts.
+ *
+ * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE` with a
+ * notification callback that cancels the operation if a dirty-but-existing
+ * file is found in the working directory. This core git command isn't
+ * quite "force" but is sensitive about some types of changes.
+ *
+ * To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
+ *
+ *
+ * There are some additional flags to modified the behavior of checkout:
+ *
+ * - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
+ * even if there are conflicts (instead of cancelling the checkout).
+ *
+ * - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not
+ * in target, baseline, or index, and not ignored) from the working dir.
+ *
+ * - GIT_CHECKOUT_REMOVE_IGNORED means remove ignored files (that are also
+ * untracked) from the working directory as well.
+ *
+ * - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
+ * already exist. Files will not be created nor deleted. This just skips
+ * applying adds, deletes, and typechanges.
+ *
+ * - GIT_CHECKOUT_DONT_UPDATE_INDEX prevents checkout from writing the
+ * updated files' information to the index.
+ *
+ * - Normally, checkout will reload the index and git attributes from disk
+ * before any operations. GIT_CHECKOUT_NO_REFRESH prevents this reload.
+ *
+ * - Unmerged index entries are conflicts. GIT_CHECKOUT_SKIP_UNMERGED skips
+ * files with unmerged index entries instead. GIT_CHECKOUT_USE_OURS and
+ * GIT_CHECKOUT_USE_THEIRS to proceed with the checkout using either the
+ * stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
+ *
+ * - GIT_CHECKOUT_DONT_OVERWRITE_IGNORED prevents ignored files from being
+ * overwritten. Normally, files that are ignored in the working directory
+ * are not considered "precious" and may be overwritten if the checkout
+ * target contains that file.
+ *
+ * - GIT_CHECKOUT_DONT_REMOVE_EXISTING prevents checkout from removing
+ * files or folders that fold to the same name on case insensitive
+ * filesystems. This can cause files to retain their existing names
+ * and write through existing symbolic links.
+ */
+typedef enum {
+ GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */
+
+ /** Allow safe updates that cannot overwrite uncommitted data */
+ GIT_CHECKOUT_SAFE = (1u << 0),
+
+ /** Allow all updates to force working directory to look like index */
+ GIT_CHECKOUT_FORCE = (1u << 1),
+
+
+ /** Allow checkout to recreate missing files */
+ GIT_CHECKOUT_RECREATE_MISSING = (1u << 2),
+
+ /** Allow checkout to make safe updates even if conflicts are found */
+ GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
+
+ /** Remove untracked files not in index (that are not ignored) */
+ GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
+
+ /** Remove ignored files not in index */
+ GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),
+
+ /** Only update existing files, don't create new ones */
+ GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
+
+ /**
+ * Normally checkout updates index entries as it goes; this stops that.
+ * Implies `GIT_CHECKOUT_DONT_WRITE_INDEX`.
+ */
+ GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),
+
+ /** Don't refresh index/config/etc before doing checkout */
+ GIT_CHECKOUT_NO_REFRESH = (1u << 9),
+
+ /** Allow checkout to skip unmerged files */
+ GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
+ /** For unmerged files, checkout stage 2 from index */
+ GIT_CHECKOUT_USE_OURS = (1u << 11),
+ /** For unmerged files, checkout stage 3 from index */
+ GIT_CHECKOUT_USE_THEIRS = (1u << 12),
+
+ /** Treat pathspec as simple list of exact match file paths */
+ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),
+
+ /** Ignore directories in use, they will be left empty */
+ GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = (1u << 18),
+
+ /** Don't overwrite ignored files that exist in the checkout target */
+ GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = (1u << 19),
+
+ /** Write normal merge files for conflicts */
+ GIT_CHECKOUT_CONFLICT_STYLE_MERGE = (1u << 20),
+
+ /** Include common ancestor data in diff3 format files for conflicts */
+ GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = (1u << 21),
+
+ /** Don't overwrite existing files or folders */
+ GIT_CHECKOUT_DONT_REMOVE_EXISTING = (1u << 22),
+
+ /** Normally checkout writes the index upon completion; this prevents that. */
+ GIT_CHECKOUT_DONT_WRITE_INDEX = (1u << 23),
+
+ /**
+ * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
+ */
+
+ /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
+ /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17),
+
+} git_checkout_strategy_t;
+
+/**
+ * Checkout notification flags
+ *
+ * Checkout will invoke an options notification callback (`notify_cb`) for
+ * certain cases - you pick which ones via `notify_flags`:
+ *
+ * - GIT_CHECKOUT_NOTIFY_CONFLICT invokes checkout on conflicting paths.
+ *
+ * - GIT_CHECKOUT_NOTIFY_DIRTY notifies about "dirty" files, i.e. those that
+ * do not need an update but no longer match the baseline. Core git
+ * displays these files when checkout runs, but won't stop the checkout.
+ *
+ * - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed.
+ *
+ * - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies about untracked files.
+ *
+ * - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.
+ *
+ * Returning a non-zero value from this callback will cancel the checkout.
+ * The non-zero return value will be propagated back and returned by the
+ * git_checkout_... call.
+ *
+ * Notification callbacks are made prior to modifying any files on disk,
+ * so canceling on any notification will still happen prior to any files
+ * being modified.
+ */
+typedef enum {
+ GIT_CHECKOUT_NOTIFY_NONE = 0,
+ GIT_CHECKOUT_NOTIFY_CONFLICT = (1u << 0),
+ GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),
+ GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
+ GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
+ GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
+
+ GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu
+} git_checkout_notify_t;
+
+typedef struct {
+ size_t mkdir_calls;
+ size_t stat_calls;
+ size_t chmod_calls;
+} git_checkout_perfdata;
+
+/** Checkout notification callback function */
+typedef int (*git_checkout_notify_cb)(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload);
+
+/** Checkout progress notification function */
+typedef void (*git_checkout_progress_cb)(
+ const char *path,
+ size_t completed_steps,
+ size_t total_steps,
+ void *payload);
+
+/** Checkout perfdata notification function */
+typedef void (*git_checkout_perfdata_cb)(
+ const git_checkout_perfdata *perfdata,
+ void *payload);
+
+/**
+ * Checkout options structure
+ *
+ * Zero out for defaults. Initialize with `GIT_CHECKOUT_OPTIONS_INIT` macro to
+ * correctly set the `version` field. E.g.
+ *
+ * git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ */
+typedef struct git_checkout_options {
+ unsigned int version;
+
+ unsigned int checkout_strategy; /**< default will be a dry run */
+
+ int disable_filters; /**< don't apply filters like CRLF conversion */
+ unsigned int dir_mode; /**< default is 0755 */
+ unsigned int file_mode; /**< default is 0644 or 0755 as dictated by blob */
+ int file_open_flags; /**< default is O_CREAT | O_TRUNC | O_WRONLY */
+
+ unsigned int notify_flags; /**< see `git_checkout_notify_t` above */
+ git_checkout_notify_cb notify_cb;
+ void *notify_payload;
+
+ /** Optional callback to notify the consumer of checkout progress. */
+ git_checkout_progress_cb progress_cb;
+ void *progress_payload;
+
+ /** When not zeroed out, array of fnmatch patterns specifying which
+ * paths should be taken into account, otherwise all files. Use
+ * GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as simple list.
+ */
+ git_strarray paths;
+
+ /** The expected content of the working directory; defaults to HEAD.
+ * If the working directory does not match this baseline information,
+ * that will produce a checkout conflict.
+ */
+ git_tree *baseline;
+
+ /** Like `baseline` above, though expressed as an index. This
+ * option overrides `baseline`.
+ */
+ git_index *baseline_index; /**< expected content of workdir, expressed as an index. */
+
+ const char *target_directory; /**< alternative checkout path to workdir */
+
+ const char *ancestor_label; /**< the name of the common ancestor side of conflicts */
+ const char *our_label; /**< the name of the "our" side of conflicts */
+ const char *their_label; /**< the name of the "their" side of conflicts */
+
+ /** Optional callback to notify the consumer of performance data. */
+ git_checkout_perfdata_cb perfdata_cb;
+ void *perfdata_payload;
+} git_checkout_options;
+
+#define GIT_CHECKOUT_OPTIONS_VERSION 1
+#define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION}
+
+/**
+* Initializes a `git_checkout_options` with default values. Equivalent to
+* creating an instance with GIT_CHECKOUT_OPTIONS_INIT.
+*
+* @param opts the `git_checkout_options` struct to initialize.
+* @param version Version of struct; pass `GIT_CHECKOUT_OPTIONS_VERSION`
+* @return Zero on success; -1 on failure.
+*/
+GIT_EXTERN(int) git_checkout_init_options(
+ git_checkout_options *opts,
+ unsigned int version);
+
+/**
+ * Updates files in the index and the working tree to match the content of
+ * the commit pointed at by HEAD.
+ *
+ * Note that this is _not_ the correct mechanism used to switch branches;
+ * do not change your `HEAD` and then call this method, that would leave
+ * you with checkout conflicts since your working directory would then
+ * appear to be dirty. Instead, checkout the target of the branch and
+ * then update `HEAD` using `git_repository_set_head` to point to the
+ * branch you checked out.
+ *
+ * @param repo repository to check out (must be non-bare)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, GIT_EUNBORNBRANCH if HEAD points to a non
+ * existing branch, non-zero value returned by `notify_cb`, or
+ * other error code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_head(
+ git_repository *repo,
+ const git_checkout_options *opts);
+
+/**
+ * Updates files in the working tree to match the content of the index.
+ *
+ * @param repo repository into which to check out (must be non-bare)
+ * @param index index to be checked out (or NULL to use repository index)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_index(
+ git_repository *repo,
+ git_index *index,
+ const git_checkout_options *opts);
+
+/**
+ * Updates files in the index and working tree to match the content of the
+ * tree pointed at by the treeish.
+ *
+ * @param repo repository to check out (must be non-bare)
+ * @param treeish a commit, tag or tree which content will be used to update
+ * the working directory (or NULL to use HEAD)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_tree(
+ git_repository *repo,
+ const git_object *treeish,
+ const git_checkout_options *opts);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_cherrypick_h__
+#define INCLUDE_git_cherrypick_h__
+
+#include "common.h"
+#include "types.h"
+#include "merge.h"
+
+/**
+ * @file git2/cherrypick.h
+ * @brief Git cherry-pick routines
+ * @defgroup git_cherrypick Git cherry-pick routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Cherry-pick options
+ */
+typedef struct {
+ unsigned int version;
+
+ /** For merge commits, the "mainline" is treated as the parent. */
+ unsigned int mainline;
+
+ git_merge_options merge_opts; /**< Options for the merging */
+ git_checkout_options checkout_opts; /**< Options for the checkout */
+} git_cherrypick_options;
+
+#define GIT_CHERRYPICK_OPTIONS_VERSION 1
+#define GIT_CHERRYPICK_OPTIONS_INIT {GIT_CHERRYPICK_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT}
+
+/**
+ * Initializes a `git_cherrypick_options` with default values. Equivalent to
+ * creating an instance with GIT_CHERRYPICK_OPTIONS_INIT.
+ *
+ * @param opts the `git_cherrypick_options` struct to initialize
+ * @param version Version of struct; pass `GIT_CHERRYPICK_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_cherrypick_init_options(
+ git_cherrypick_options *opts,
+ unsigned int version);
+
+/**
+ * Cherry-picks the given commit against the given "our" commit, producing an
+ * index that reflects the result of the cherry-pick.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo the repository that contains the given commits
+ * @param cherrypick_commit the commit to cherry-pick
+ * @param our_commit the commit to revert against (eg, HEAD)
+ * @param mainline the parent of the revert commit, if it is a merge
+ * @param merge_options the merge options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_cherrypick_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *cherrypick_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_options);
+
+/**
+ * Cherry-pick the given commit, producing changes in the index and working directory.
+ *
+ * @param repo the repository to cherry-pick
+ * @param commit the commit to cherry-pick
+ * @param cherrypick_options the cherry-pick options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_cherrypick(
+ git_repository *repo,
+ git_commit *commit,
+ const git_cherrypick_options *cherrypick_options);
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_clone_h__
+#define INCLUDE_git_clone_h__
+
+#include "common.h"
+#include "types.h"
+#include "indexer.h"
+#include "checkout.h"
+#include "remote.h"
+#include "transport.h"
+
+
+/**
+ * @file git2/clone.h
+ * @brief Git cloning routines
+ * @defgroup git_clone Git cloning routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Options for bypassing the git-aware transport on clone. Bypassing
+ * it means that instead of a fetch, libgit2 will copy the object
+ * database directory instead of figuring out what it needs, which is
+ * faster. If possible, it will hardlink the files to save space.
+ */
+typedef enum {
+ /**
+ * Auto-detect (default), libgit2 will bypass the git-aware
+ * transport for local paths, but use a normal fetch for
+ * `file://` urls.
+ */
+ GIT_CLONE_LOCAL_AUTO,
+ /**
+ * Bypass the git-aware transport even for a `file://` url.
+ */
+ GIT_CLONE_LOCAL,
+ /**
+ * Do no bypass the git-aware transport
+ */
+ GIT_CLONE_NO_LOCAL,
+ /**
+ * Bypass the git-aware transport, but do not try to use
+ * hardlinks.
+ */
+ GIT_CLONE_LOCAL_NO_LINKS,
+} git_clone_local_t;
+
+/**
+ * The signature of a function matching git_remote_create, with an additional
+ * void* as a callback payload.
+ *
+ * Callers of git_clone may provide a function matching this signature to override
+ * the remote creation and customization process during a clone operation.
+ *
+ * @param out the resulting remote
+ * @param repo the repository in which to create the remote
+ * @param name the remote's name
+ * @param url the remote's url
+ * @param payload an opaque payload
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+typedef int (*git_remote_create_cb)(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url,
+ void *payload);
+
+/**
+ * The signature of a function matchin git_repository_init, with an
+ * aditional void * as callback payload.
+ *
+ * Callers of git_clone my provide a function matching this signature
+ * to override the repository creation and customization process
+ * during a clone operation.
+ *
+ * @param out the resulting repository
+ * @param path path in which to create the repository
+ * @param bare whether the repository is bare. This is the value from the clone options
+ * @param payload payload specified by the options
+ * @return 0, or a negative value to indicate error
+ */
+typedef int (*git_repository_create_cb)(
+ git_repository **out,
+ const char *path,
+ int bare,
+ void *payload);
+
+/**
+ * Clone options structure
+ *
+ * Use the GIT_CLONE_OPTIONS_INIT to get the default settings, like this:
+ *
+ * git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ */
+typedef struct git_clone_options {
+ unsigned int version;
+
+ /**
+ * These options are passed to the checkout step. To disable
+ * checkout, set the `checkout_strategy` to
+ * `GIT_CHECKOUT_NONE`.
+ */
+ git_checkout_options checkout_opts;
+
+ /**
+ * Options which control the fetch, including callbacks.
+ *
+ * The callbacks are used for reporting fetch progress, and for acquiring
+ * credentials in the event they are needed.
+ */
+ git_fetch_options fetch_opts;
+
+ /**
+ * Set to zero (false) to create a standard repo, or non-zero
+ * for a bare repo
+ */
+ int bare;
+
+ /**
+ * Whether to use a fetch or copy the object database.
+ */
+ git_clone_local_t local;
+
+ /**
+ * The name of the branch to checkout. NULL means use the
+ * remote's default branch.
+ */
+ const char* checkout_branch;
+
+ /**
+ * A callback used to create the new repository into which to
+ * clone. If NULL, the 'bare' field will be used to determine
+ * whether to create a bare repository.
+ */
+ git_repository_create_cb repository_cb;
+
+ /**
+ * An opaque payload to pass to the git_repository creation callback.
+ * This parameter is ignored unless repository_cb is non-NULL.
+ */
+ void *repository_cb_payload;
+
+ /**
+ * A callback used to create the git_remote, prior to its being
+ * used to perform the clone operation. See the documentation for
+ * git_remote_create_cb for details. This parameter may be NULL,
+ * indicating that git_clone should provide default behavior.
+ */
+ git_remote_create_cb remote_cb;
+
+ /**
+ * An opaque payload to pass to the git_remote creation callback.
+ * This parameter is ignored unless remote_cb is non-NULL.
+ */
+ void *remote_cb_payload;
+} git_clone_options;
+
+#define GIT_CLONE_OPTIONS_VERSION 1
+#define GIT_CLONE_OPTIONS_INIT { GIT_CLONE_OPTIONS_VERSION, \
+ { GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
+ GIT_FETCH_OPTIONS_INIT }
+
+/**
+ * Initializes a `git_clone_options` with default values. Equivalent to
+ * creating an instance with GIT_CLONE_OPTIONS_INIT.
+ *
+ * @param opts The `git_clone_options` struct to initialize
+ * @param version Version of struct; pass `GIT_CLONE_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_clone_init_options(
+ git_clone_options *opts,
+ unsigned int version);
+
+/**
+ * Clone a remote repository.
+ *
+ * By default this creates its repository and initial remote to match
+ * git's defaults. You can use the options in the callback to
+ * customize how these are created.
+ *
+ * @param out pointer that will receive the resulting repository object
+ * @param url the remote repository to clone
+ * @param local_path local directory to clone to
+ * @param options configuration options for the clone. If NULL, the
+ * function works as though GIT_OPTIONS_INIT were passed.
+ * @return 0 on success, any non-zero return value from a callback
+ * function, or a negative value to indicate an error (use
+ * `giterr_last` for a detailed error message)
+ */
+GIT_EXTERN(int) git_clone(
+ git_repository **out,
+ const char *url,
+ const char *local_path,
+ const git_clone_options *options);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_commit_h__
+#define INCLUDE_git_commit_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "object.h"
+
+/**
+ * @file git2/commit.h
+ * @brief Git commit parsing, formatting routines
+ * @defgroup git_commit Git commit parsing, formatting routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a commit object from a repository.
+ *
+ * The returned object should be released with `git_commit_free` when no
+ * longer needed.
+ *
+ * @param commit pointer to the looked up commit
+ * @param repo the repo to use when locating the commit.
+ * @param id identity of the commit to locate. If the object is
+ * an annotated tag it will be peeled back to the commit.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_lookup(
+ git_commit **commit, git_repository *repo, const git_oid *id);
+
+/**
+ * Lookup a commit object from a repository, given a prefix of its
+ * identifier (short id).
+ *
+ * The returned object should be released with `git_commit_free` when no
+ * longer needed.
+ *
+ * @see git_object_lookup_prefix
+ *
+ * @param commit pointer to the looked up commit
+ * @param repo the repo to use when locating the commit.
+ * @param id identity of the commit to locate. If the object is
+ * an annotated tag it will be peeled back to the commit.
+ * @param len the length of the short identifier
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_lookup_prefix(
+ git_commit **commit, git_repository *repo, const git_oid *id, size_t len);
+
+/**
+ * Close an open commit
+ *
+ * This is a wrapper around git_object_free()
+ *
+ * IMPORTANT:
+ * It *is* necessary to call this method when you stop
+ * using a commit. Failure to do so will cause a memory leak.
+ *
+ * @param commit the commit to close
+ */
+
+GIT_EXTERN(void) git_commit_free(git_commit *commit);
+
+/**
+ * Get the id of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return object identity for the commit.
+ */
+GIT_EXTERN(const git_oid *) git_commit_id(const git_commit *commit);
+
+/**
+ * Get the repository that contains the commit.
+ *
+ * @param commit A previously loaded commit.
+ * @return Repository that contains this commit.
+ */
+GIT_EXTERN(git_repository *) git_commit_owner(const git_commit *commit);
+
+/**
+ * Get the encoding for the message of a commit,
+ * as a string representing a standard encoding name.
+ *
+ * The encoding may be NULL if the `encoding` header
+ * in the commit is missing; in that case UTF-8 is assumed.
+ *
+ * @param commit a previously loaded commit.
+ * @return NULL, or the encoding
+ */
+GIT_EXTERN(const char *) git_commit_message_encoding(const git_commit *commit);
+
+/**
+ * Get the full message of a commit.
+ *
+ * The returned message will be slightly prettified by removing any
+ * potential leading newlines.
+ *
+ * @param commit a previously loaded commit.
+ * @return the message of a commit
+ */
+GIT_EXTERN(const char *) git_commit_message(const git_commit *commit);
+
+/**
+ * Get the full raw message of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return the raw message of a commit
+ */
+GIT_EXTERN(const char *) git_commit_message_raw(const git_commit *commit);
+
+/**
+ * Get the short "summary" of the git commit message.
+ *
+ * The returned message is the summary of the commit, comprising the
+ * first paragraph of the message with whitespace trimmed and squashed.
+ *
+ * @param commit a previously loaded commit.
+ * @return the summary of a commit or NULL on error
+ */
+GIT_EXTERN(const char *) git_commit_summary(git_commit *commit);
+
+/**
+ * Get the long "body" of the git commit message.
+ *
+ * The returned message is the body of the commit, comprising
+ * everything but the first paragraph of the message. Leading and
+ * trailing whitespaces are trimmed.
+ *
+ * @param commit a previously loaded commit.
+ * @return the body of a commit or NULL when no the message only
+ * consists of a summary
+ */
+GIT_EXTERN(const char *) git_commit_body(git_commit *commit);
+
+/**
+ * Get the commit time (i.e. committer time) of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return the time of a commit
+ */
+GIT_EXTERN(git_time_t) git_commit_time(const git_commit *commit);
+
+/**
+ * Get the commit timezone offset (i.e. committer's preferred timezone) of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return positive or negative timezone offset, in minutes from UTC
+ */
+GIT_EXTERN(int) git_commit_time_offset(const git_commit *commit);
+
+/**
+ * Get the committer of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return the committer of a commit
+ */
+GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit);
+
+/**
+ * Get the author of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return the author of a commit
+ */
+GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit);
+
+/**
+ * Get the full raw text of the commit header.
+ *
+ * @param commit a previously loaded commit
+ * @return the header text of the commit
+ */
+GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit);
+
+/**
+ * Get the tree pointed to by a commit.
+ *
+ * @param tree_out pointer where to store the tree object
+ * @param commit a previously loaded commit.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, const git_commit *commit);
+
+/**
+ * Get the id of the tree pointed to by a commit. This differs from
+ * `git_commit_tree` in that no attempts are made to fetch an object
+ * from the ODB.
+ *
+ * @param commit a previously loaded commit.
+ * @return the id of tree pointed to by commit.
+ */
+GIT_EXTERN(const git_oid *) git_commit_tree_id(const git_commit *commit);
+
+/**
+ * Get the number of parents of this commit
+ *
+ * @param commit a previously loaded commit.
+ * @return integer of count of parents
+ */
+GIT_EXTERN(unsigned int) git_commit_parentcount(const git_commit *commit);
+
+/**
+ * Get the specified parent of the commit.
+ *
+ * @param out Pointer where to store the parent commit
+ * @param commit a previously loaded commit.
+ * @param n the position of the parent (from 0 to `parentcount`)
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_parent(
+ git_commit **out,
+ const git_commit *commit,
+ unsigned int n);
+
+/**
+ * Get the oid of a specified parent for a commit. This is different from
+ * `git_commit_parent`, which will attempt to load the parent commit from
+ * the ODB.
+ *
+ * @param commit a previously loaded commit.
+ * @param n the position of the parent (from 0 to `parentcount`)
+ * @return the id of the parent, NULL on error.
+ */
+GIT_EXTERN(const git_oid *) git_commit_parent_id(
+ const git_commit *commit,
+ unsigned int n);
+
+/**
+ * Get the commit object that is the <n>th generation ancestor
+ * of the named commit object, following only the first parents.
+ * The returned commit has to be freed by the caller.
+ *
+ * Passing `0` as the generation number returns another instance of the
+ * base commit itself.
+ *
+ * @param ancestor Pointer where to store the ancestor commit
+ * @param commit a previously loaded commit.
+ * @param n the requested generation
+ * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists
+ * or an error code
+ */
+GIT_EXTERN(int) git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n);
+
+/**
+ * Get an arbitrary header field
+ *
+ * @param out the buffer to fill
+ * @param commit the commit to look in
+ * @param field the header field to return
+ * @return 0 on succeess, GIT_ENOTFOUND if the field does not exist,
+ * or an error code
+ */
+GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field);
+
+/**
+ * Extract the signature from a commit
+ *
+ * If the id is not for a commit, the error class will be
+ * `GITERR_INVALID`. If the commit does not have a signature, the
+ * error class will be `GITERR_OBJECT`.
+ *
+ * @param signature the signature block
+ * @param signed_data signed data; this is the commit contents minus the signature block
+ * @param repo the repository in which the commit exists
+ * @param commit_id the commit from which to extract the data
+ * @param field the name of the header field containing the signature
+ * block; pass `NULL` to extract the default 'gpgsig'
+ * @return 0 on success, GIT_ENOTFOUND if the id is not for a commit
+ * or the commit does not have a signature.
+ */
+GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field);
+
+/**
+ * Create new commit in the repository from a list of `git_object` pointers
+ *
+ * The message will **not** be cleaned up automatically. You can do that
+ * with the `git_message_prettify()` function.
+ *
+ * @param id Pointer in which to store the OID of the newly created commit
+ *
+ * @param repo Repository where to store the commit
+ *
+ * @param update_ref If not NULL, name of the reference that
+ * will be updated to point to this commit. If the reference
+ * is not direct, it will be resolved to a direct reference.
+ * Use "HEAD" to update the HEAD of the current branch and
+ * make it point to this commit. If the reference doesn't
+ * exist yet, it will be created. If it does exist, the first
+ * parent must be the tip of this branch.
+ *
+ * @param author Signature with author and author time of commit
+ *
+ * @param committer Signature with committer and * commit time of commit
+ *
+ * @param message_encoding The encoding for the message in the
+ * commit, represented with a standard encoding name.
+ * E.g. "UTF-8". If NULL, no encoding header is written and
+ * UTF-8 is assumed.
+ *
+ * @param message Full message for this commit
+ *
+ * @param tree An instance of a `git_tree` object that will
+ * be used as the tree for the commit. This tree object must
+ * also be owned by the given `repo`.
+ *
+ * @param parent_count Number of parents for this commit
+ *
+ * @param parents Array of `parent_count` pointers to `git_commit`
+ * objects that will be used as the parents for this commit. This
+ * array may be NULL if `parent_count` is 0 (root commit). All the
+ * given commits must be owned by the `repo`.
+ *
+ * @return 0 or an error code
+ * The created commit will be written to the Object Database and
+ * the given reference will be updated to point to it
+ */
+GIT_EXTERN(int) git_commit_create(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[]);
+
+/**
+ * Create new commit in the repository using a variable argument list.
+ *
+ * The message will **not** be cleaned up automatically. You can do that
+ * with the `git_message_prettify()` function.
+ *
+ * The parents for the commit are specified as a variable list of pointers
+ * to `const git_commit *`. Note that this is a convenience method which may
+ * not be safe to export for certain languages or compilers
+ *
+ * All other parameters remain the same as `git_commit_create()`.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_create_v(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ ...);
+
+/**
+ * Amend an existing commit by replacing only non-NULL values.
+ *
+ * This creates a new commit that is exactly the same as the old commit,
+ * except that any non-NULL values will be updated. The new commit has
+ * the same parents as the old commit.
+ *
+ * The `update_ref` value works as in the regular `git_commit_create()`,
+ * updating the ref to point to the newly rewritten commit. If you want
+ * to amend a commit that is not currently the tip of the branch and then
+ * rewrite the following commits to reach a ref, pass this as NULL and
+ * update the rest of the commit chain and ref separately.
+ *
+ * Unlike `git_commit_create()`, the `author`, `committer`, `message`,
+ * `message_encoding`, and `tree` parameters can be NULL in which case this
+ * will use the values from the original `commit_to_amend`.
+ *
+ * All parameters have the same meanings as in `git_commit_create()`.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_amend(
+ git_oid *id,
+ const git_commit *commit_to_amend,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree);
+
+/**
+ * Create a commit and write it into a buffer
+ *
+ * Create a commit as with `git_commit_create()` but instead of
+ * writing it to the objectdb, write the contents of the object into a
+ * buffer.
+ *
+ * @param out the buffer into which to write the commit object content
+ *
+ * @param repo Repository where the referenced tree and parents live
+ *
+ * @param author Signature with author and author time of commit
+ *
+ * @param committer Signature with committer and * commit time of commit
+ *
+ * @param message_encoding The encoding for the message in the
+ * commit, represented with a standard encoding name.
+ * E.g. "UTF-8". If NULL, no encoding header is written and
+ * UTF-8 is assumed.
+ *
+ * @param message Full message for this commit
+ *
+ * @param tree An instance of a `git_tree` object that will
+ * be used as the tree for the commit. This tree object must
+ * also be owned by the given `repo`.
+ *
+ * @param parent_count Number of parents for this commit
+ *
+ * @param parents Array of `parent_count` pointers to `git_commit`
+ * objects that will be used as the parents for this commit. This
+ * array may be NULL if `parent_count` is 0 (root commit). All the
+ * given commits must be owned by the `repo`.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_create_buffer(
+ git_buf *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[]);
+
+/**
+ * Create a commit object from the given buffer and signature
+ *
+ * Given the unsigned commit object's contents, its signature and the
+ * header field in which to store the signature, attach the signature
+ * to the commit and write it into the given repository.
+ *
+ * @param out the resulting commit id
+ * @param commit_content the content of the unsigned commit object
+ * @param signature the signature to add to the commit
+ * @param signature_field which header field should contain this
+ * signature. Leave `NULL` for the default of "gpgsig"
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_commit_create_with_signature(
+ git_oid *out,
+ git_repository *repo,
+ const char *commit_content,
+ const char *signature,
+ const char *signature_field);
+
+/**
+ * Create an in-memory copy of a commit. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the commit
+ * @param source Original commit to copy
+ */
+GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_common_h__
+#define INCLUDE_git_common_h__
+
+#include <time.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+# define GIT_BEGIN_DECL extern "C" {
+# define GIT_END_DECL }
+#else
+ /** Start declarations in C mode */
+# define GIT_BEGIN_DECL /* empty */
+ /** End declarations in C mode */
+# define GIT_END_DECL /* empty */
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER < 1800
+ GIT_BEGIN_DECL
+# include "inttypes.h"
+ GIT_END_DECL
+/** This check is needed for importing this file in an iOS/OS X framework throws an error in Xcode otherwise.*/
+#elif !defined(__CLANG_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#ifdef DOCURIUM
+/*
+ * This is so clang's doc parser acknowledges comments on functions
+ * with size_t parameters.
+ */
+typedef size_t size_t;
+#endif
+
+/** Declare a public function exported for application use. */
+#if __GNUC__ >= 4
+# define GIT_EXTERN(type) extern \
+ __attribute__((visibility("default"))) \
+ type
+#elif defined(_MSC_VER)
+# define GIT_EXTERN(type) __declspec(dllexport) type
+#else
+# define GIT_EXTERN(type) extern type
+#endif
+
+/** Declare a function's takes printf style arguments. */
+#ifdef __GNUC__
+# define GIT_FORMAT_PRINTF(a,b) __attribute__((format (printf, a, b)))
+#else
+# define GIT_FORMAT_PRINTF(a,b) /* empty */
+#endif
+
+#if (defined(_WIN32)) && !defined(__CYGWIN__)
+#define GIT_WIN32 1
+#endif
+
+#ifdef __amigaos4__
+#include <netinet/in.h>
+#endif
+
+/**
+ * @file git2/common.h
+ * @brief Git common platform definitions
+ * @defgroup git_common Git common platform definitions
+ * @ingroup Git
+ * @{
+ */
+
+GIT_BEGIN_DECL
+
+/**
+ * The separator used in path list strings (ie like in the PATH
+ * environment variable). A semi-colon ";" is used on Windows, and
+ * a colon ":" for all other systems.
+ */
+#ifdef GIT_WIN32
+#define GIT_PATH_LIST_SEPARATOR ';'
+#else
+#define GIT_PATH_LIST_SEPARATOR ':'
+#endif
+
+/**
+ * The maximum length of a valid git path.
+ */
+#define GIT_PATH_MAX 4096
+
+/**
+ * The string representation of the null object ID.
+ */
+#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
+
+/**
+ * Return the version of the libgit2 library
+ * being currently used.
+ *
+ * @param major Store the major version number
+ * @param minor Store the minor version number
+ * @param rev Store the revision (patch) number
+ */
+GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev);
+
+/**
+ * Combinations of these values describe the features with which libgit2
+ * was compiled
+ */
+typedef enum {
+ /**
+ * If set, libgit2 was built thread-aware and can be safely used from multiple
+ * threads.
+ */
+ GIT_FEATURE_THREADS = (1 << 0),
+ /**
+ * If set, libgit2 was built with and linked against a TLS implementation.
+ * Custom TLS streams may still be added by the user to support HTTPS
+ * regardless of this.
+ */
+ GIT_FEATURE_HTTPS = (1 << 1),
+ /**
+ * If set, libgit2 was built with and linked against libssh2. A custom
+ * transport may still be added by the user to support libssh2 regardless of
+ * this.
+ */
+ GIT_FEATURE_SSH = (1 << 2),
+ /**
+ * If set, libgit2 was built with support for sub-second resolution in file
+ * modification times.
+ */
+ GIT_FEATURE_NSEC = (1 << 3),
+} git_feature_t;
+
+/**
+ * Query compile time options for libgit2.
+ *
+ * @return A combination of GIT_FEATURE_* values.
+ *
+ * - GIT_FEATURE_THREADS
+ * Libgit2 was compiled with thread support. Note that thread support is
+ * still to be seen as a 'work in progress' - basic object lookups are
+ * believed to be threadsafe, but other operations may not be.
+ *
+ * - GIT_FEATURE_HTTPS
+ * Libgit2 supports the https:// protocol. This requires the openssl
+ * library to be found when compiling libgit2.
+ *
+ * - GIT_FEATURE_SSH
+ * Libgit2 supports the SSH protocol for network operations. This requires
+ * the libssh2 library to be found when compiling libgit2
+ */
+GIT_EXTERN(int) git_libgit2_features(void);
+
+/**
+ * Global library options
+ *
+ * These are used to select which global option to set or get and are
+ * used in `git_libgit2_opts()`.
+ */
+typedef enum {
+ GIT_OPT_GET_MWINDOW_SIZE,
+ GIT_OPT_SET_MWINDOW_SIZE,
+ GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+ GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+ GIT_OPT_GET_SEARCH_PATH,
+ GIT_OPT_SET_SEARCH_PATH,
+ GIT_OPT_SET_CACHE_OBJECT_LIMIT,
+ GIT_OPT_SET_CACHE_MAX_SIZE,
+ GIT_OPT_ENABLE_CACHING,
+ GIT_OPT_GET_CACHED_MEMORY,
+ GIT_OPT_GET_TEMPLATE_PATH,
+ GIT_OPT_SET_TEMPLATE_PATH,
+ GIT_OPT_SET_SSL_CERT_LOCATIONS,
+ GIT_OPT_SET_USER_AGENT,
+ GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+ GIT_OPT_SET_SSL_CIPHERS,
+ GIT_OPT_GET_USER_AGENT,
+} git_libgit2_opt_t;
+
+/**
+ * Set or query a library global option
+ *
+ * Available options:
+ *
+ * * opts(GIT_OPT_GET_MWINDOW_SIZE, size_t *):
+ *
+ * > Get the maximum mmap window size
+ *
+ * * opts(GIT_OPT_SET_MWINDOW_SIZE, size_t):
+ *
+ * > Set the maximum mmap window size
+ *
+ * * opts(GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, size_t *):
+ *
+ * > Get the maximum memory that will be mapped in total by the library
+ *
+ * * opts(GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size_t):
+ *
+ * >Set the maximum amount of memory that can be mapped at any time
+ * by the library
+ *
+ * * opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf)
+ *
+ * > Get the search path for a given level of config data. "level" must
+ * > be one of `GIT_CONFIG_LEVEL_SYSTEM`, `GIT_CONFIG_LEVEL_GLOBAL`,
+ * > `GIT_CONFIG_LEVEL_XDG`, or `GIT_CONFIG_LEVEL_PROGRAMDATA`.
+ * > The search path is written to the `out` buffer.
+ *
+ * * opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path)
+ *
+ * > Set the search path for a level of config data. The search path
+ * > applied to shared attributes and ignore files, too.
+ * >
+ * > - `path` lists directories delimited by GIT_PATH_LIST_SEPARATOR.
+ * > Pass NULL to reset to the default (generally based on environment
+ * > variables). Use magic path `$PATH` to include the old value
+ * > of the path (if you want to prepend or append, for instance).
+ * >
+ * > - `level` must be `GIT_CONFIG_LEVEL_SYSTEM`,
+ * > `GIT_CONFIG_LEVEL_GLOBAL`, `GIT_CONFIG_LEVEL_XDG`, or
+ * > `GIT_CONFIG_LEVEL_PROGRAMDATA`.
+ *
+ * * opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, git_otype type, size_t size)
+ *
+ * > Set the maximum data size for the given type of object to be
+ * > considered eligible for caching in memory. Setting to value to
+ * > zero means that that type of object will not be cached.
+ * > Defaults to 0 for GIT_OBJ_BLOB (i.e. won't cache blobs) and 4k
+ * > for GIT_OBJ_COMMIT, GIT_OBJ_TREE, and GIT_OBJ_TAG.
+ *
+ * * opts(GIT_OPT_SET_CACHE_MAX_SIZE, ssize_t max_storage_bytes)
+ *
+ * > Set the maximum total data size that will be cached in memory
+ * > across all repositories before libgit2 starts evicting objects
+ * > from the cache. This is a soft limit, in that the library might
+ * > briefly exceed it, but will start aggressively evicting objects
+ * > from cache when that happens. The default cache size is 256MB.
+ *
+ * * opts(GIT_OPT_ENABLE_CACHING, int enabled)
+ *
+ * > Enable or disable caching completely.
+ * >
+ * > Because caches are repository-specific, disabling the cache
+ * > cannot immediately clear all cached objects, but each cache will
+ * > be cleared on the next attempt to update anything in it.
+ *
+ * * opts(GIT_OPT_GET_CACHED_MEMORY, ssize_t *current, ssize_t *allowed)
+ *
+ * > Get the current bytes in cache and the maximum that would be
+ * > allowed in the cache.
+ *
+ * * opts(GIT_OPT_GET_TEMPLATE_PATH, git_buf *out)
+ *
+ * > Get the default template path.
+ * > The path is written to the `out` buffer.
+ *
+ * * opts(GIT_OPT_SET_TEMPLATE_PATH, const char *path)
+ *
+ * > Set the default template path.
+ * >
+ * > - `path` directory of template.
+ *
+ * * opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, const char *file, const char *path)
+ *
+ * > Set the SSL certificate-authority locations.
+ * >
+ * > - `file` is the location of a file containing several
+ * > certificates concatenated together.
+ * > - `path` is the location of a directory holding several
+ * > certificates, one per file.
+ * >
+ * > Either parameter may be `NULL`, but not both.
+ *
+ * * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent)
+ *
+ * > Set the value of the User-Agent header. This value will be
+ * > appended to "git/1.0", for compatibility with other git clients.
+ * >
+ * > - `user_agent` is the value that will be delivered as the
+ * > User-Agent header on HTTP requests.
+ *
+ * * opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, int enabled)
+ *
+ * > Enable strict input validation when creating new objects
+ * > to ensure that all inputs to the new objects are valid. For
+ * > example, when this is enabled, the parent(s) and tree inputs
+ * > will be validated when creating a new commit. This defaults
+ * > to enabled.
+ *
+ * * opts(GIT_OPT_SET_SSL_CIPHERS, const char *ciphers)
+ *
+ * > Set the SSL ciphers use for HTTPS connections.
+ * >
+ * > - `ciphers` is the list of ciphers that are eanbled.
+ *
+ * @param option Option key
+ * @param ... value to set the option
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_libgit2_opts(int option, ...);
+
+/** @} */
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_config_h__
+#define INCLUDE_git_config_h__
+
+#include "common.h"
+#include "types.h"
+#include "buffer.h"
+
+/**
+ * @file git2/config.h
+ * @brief Git config management routines
+ * @defgroup git_config Git config management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Priority level of a config file.
+ * These priority levels correspond to the natural escalation logic
+ * (from higher to lower) when searching for config entries in git.git.
+ *
+ * git_config_open_default() and git_repository_config() honor those
+ * priority levels as well.
+ */
+typedef enum {
+ /** System-wide on Windows, for compatibility with portable git */
+ GIT_CONFIG_LEVEL_PROGRAMDATA = 1,
+
+ /** System-wide configuration file; /etc/gitconfig on Linux systems */
+ GIT_CONFIG_LEVEL_SYSTEM = 2,
+
+ /** XDG compatible configuration file; typically ~/.config/git/config */
+ GIT_CONFIG_LEVEL_XDG = 3,
+
+ /** User-specific configuration file (also called Global configuration
+ * file); typically ~/.gitconfig
+ */
+ GIT_CONFIG_LEVEL_GLOBAL = 4,
+
+ /** Repository specific configuration file; $WORK_DIR/.git/config on
+ * non-bare repos
+ */
+ GIT_CONFIG_LEVEL_LOCAL = 5,
+
+ /** Application specific configuration file; freely defined by applications
+ */
+ GIT_CONFIG_LEVEL_APP = 6,
+
+ /** Represents the highest level available config file (i.e. the most
+ * specific config file available that actually is loaded)
+ */
+ GIT_CONFIG_HIGHEST_LEVEL = -1,
+} git_config_level_t;
+
+/**
+ * An entry in a configuration file
+ */
+typedef struct git_config_entry {
+ const char *name; /**< Name of the entry (normalised) */
+ const char *value; /**< String value of the entry */
+ git_config_level_t level; /**< Which config file this was found in */
+ void (*free)(struct git_config_entry *entry); /**< Free function for this entry */
+ void *payload; /**< Opaque value for the free function. Do not read or write */
+} git_config_entry;
+
+/**
+ * Free a config entry
+ */
+GIT_EXTERN(void) git_config_entry_free(git_config_entry *);
+
+typedef int (*git_config_foreach_cb)(const git_config_entry *, void *);
+typedef struct git_config_iterator git_config_iterator;
+
+/**
+ * Config var type
+ */
+typedef enum {
+ GIT_CVAR_FALSE = 0,
+ GIT_CVAR_TRUE = 1,
+ GIT_CVAR_INT32,
+ GIT_CVAR_STRING
+} git_cvar_t;
+
+/**
+ * Mapping from config variables to values.
+ */
+typedef struct {
+ git_cvar_t cvar_type;
+ const char *str_match;
+ int map_value;
+} git_cvar_map;
+
+/**
+ * Locate the path to the global configuration file
+ *
+ * The user or global configuration file is usually
+ * located in `$HOME/.gitconfig`.
+ *
+ * This method will try to guess the full path to that
+ * file, if the file exists. The returned path
+ * may be used on any `git_config` call to load the
+ * global configuration file.
+ *
+ * This method will not guess the path to the xdg compatible
+ * config file (.config/git/config).
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a global configuration file has been found. Its path will be stored in `out`.
+ */
+GIT_EXTERN(int) git_config_find_global(git_buf *out);
+
+/**
+ * Locate the path to the global xdg compatible configuration file
+ *
+ * The xdg compatible configuration file is usually
+ * located in `$HOME/.config/git/config`.
+ *
+ * This method will try to guess the full path to that
+ * file, if the file exists. The returned path
+ * may be used on any `git_config` call to load the
+ * xdg compatible configuration file.
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a xdg compatible configuration file has been
+ * found. Its path will be stored in `out`.
+ */
+GIT_EXTERN(int) git_config_find_xdg(git_buf *out);
+
+/**
+ * Locate the path to the system configuration file
+ *
+ * If /etc/gitconfig doesn't exist, it will look for
+ * %PROGRAMFILES%\Git\etc\gitconfig.
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a system configuration file has been
+ * found. Its path will be stored in `out`.
+ */
+GIT_EXTERN(int) git_config_find_system(git_buf *out);
+
+/**
+ * Locate the path to the configuration file in ProgramData
+ *
+ * Look for the file in %PROGRAMDATA%\Git\config used by portable git.
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a ProgramData configuration file has been
+ * found. Its path will be stored in `out`.
+ */
+GIT_EXTERN(int) git_config_find_programdata(git_buf *out);
+
+/**
+ * Open the global, XDG and system configuration files
+ *
+ * Utility wrapper that finds the global, XDG and system configuration files
+ * and opens them into a single prioritized config object that can be
+ * used when accessing default config data outside a repository.
+ *
+ * @param out Pointer to store the config instance
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_open_default(git_config **out);
+
+/**
+ * Allocate a new configuration object
+ *
+ * This object is empty, so you have to add a file to it before you
+ * can do anything with it.
+ *
+ * @param out pointer to the new configuration
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_new(git_config **out);
+
+/**
+ * Add an on-disk config file instance to an existing config
+ *
+ * The on-disk file pointed at by `path` will be opened and
+ * parsed; it's expected to be a native Git config file following
+ * the default Git config syntax (see man git-config).
+ *
+ * If the file does not exist, the file will still be added and it
+ * will be created the first time we write to it.
+ *
+ * Note that the configuration object will free the file
+ * automatically.
+ *
+ * Further queries on this config object will access each
+ * of the config file instances in order (instances with
+ * a higher priority level will be accessed first).
+ *
+ * @param cfg the configuration to add the file to
+ * @param path path to the configuration file to add
+ * @param level the priority level of the backend
+ * @param force replace config file at the given priority level
+ * @return 0 on success, GIT_EEXISTS when adding more than one file
+ * for a given priority level (and force_replace set to 0),
+ * GIT_ENOTFOUND when the file doesn't exist or error code
+ */
+GIT_EXTERN(int) git_config_add_file_ondisk(
+ git_config *cfg,
+ const char *path,
+ git_config_level_t level,
+ int force);
+
+/**
+ * Create a new config instance containing a single on-disk file
+ *
+ * This method is a simple utility wrapper for the following sequence
+ * of calls:
+ * - git_config_new
+ * - git_config_add_file_ondisk
+ *
+ * @param out The configuration instance to create
+ * @param path Path to the on-disk file to open
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path);
+
+/**
+ * Build a single-level focused config object from a multi-level one.
+ *
+ * The returned config object can be used to perform get/set/delete operations
+ * on a single specific level.
+ *
+ * Getting several times the same level from the same parent multi-level config
+ * will return different config instances, but containing the same config_file
+ * instance.
+ *
+ * @param out The configuration instance to create
+ * @param parent Multi-level config to search for the given level
+ * @param level Configuration level to search for
+ * @return 0, GIT_ENOTFOUND if the passed level cannot be found in the
+ * multi-level parent config, or an error code
+ */
+GIT_EXTERN(int) git_config_open_level(
+ git_config **out,
+ const git_config *parent,
+ git_config_level_t level);
+
+/**
+ * Open the global/XDG configuration file according to git's rules
+ *
+ * Git allows you to store your global configuration at
+ * `$HOME/.config` or `$XDG_CONFIG_HOME/git/config`. For backwards
+ * compatability, the XDG file shouldn't be used unless the use has
+ * created it explicitly. With this function you'll open the correct
+ * one to write to.
+ *
+ * @param out pointer in which to store the config object
+ * @param config the config object in which to look
+ */
+GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config);
+
+/**
+ * Create a snapshot of the configuration
+ *
+ * Create a snapshot of the current state of a configuration, which
+ * allows you to look into a consistent view of the configuration for
+ * looking up complex values (e.g. a remote, submodule).
+ *
+ * The string returned when querying such a config object is valid
+ * until it is freed.
+ *
+ * @param out pointer in which to store the snapshot config object
+ * @param config configuration to snapshot
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_snapshot(git_config **out, git_config *config);
+
+/**
+ * Free the configuration and its associated memory and files
+ *
+ * @param cfg the configuration to free
+ */
+GIT_EXTERN(void) git_config_free(git_config *cfg);
+
+/**
+ * Get the git_config_entry of a config variable.
+ *
+ * Free the git_config_entry after use with `git_config_entry_free()`.
+ *
+ * @param out pointer to the variable git_config_entry
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_entry(
+ git_config_entry **out,
+ const git_config *cfg,
+ const char *name);
+
+/**
+ * Get the value of an integer config variable.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the variable where the value should be stored
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_int32(int32_t *out, const git_config *cfg, const char *name);
+
+/**
+ * Get the value of a long integer config variable.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the variable where the value should be stored
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_int64(int64_t *out, const git_config *cfg, const char *name);
+
+/**
+ * Get the value of a boolean config variable.
+ *
+ * This function uses the usual C convention of 0 being false and
+ * anything else true.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the variable where the value should be stored
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char *name);
+
+/**
+ * Get the value of a path config variable.
+ *
+ * A leading '~' will be expanded to the global search path (which
+ * defaults to the user's home directory but can be overridden via
+ * `git_libgit2_opts()`.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out the buffer in which to store the result
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_path(git_buf *out, const git_config *cfg, const char *name);
+
+/**
+ * Get the value of a string config variable.
+ *
+ * This function can only be used on snapshot config objects. The
+ * string is owned by the config and should not be freed by the
+ * user. The pointer will be valid until the config is freed.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the string
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name);
+
+/**
+ * Get the value of a string config variable.
+ *
+ * The value of the config will be copied into the buffer.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out buffer in which to store the string
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_string_buf(git_buf *out, const git_config *cfg, const char *name);
+
+/**
+ * Get each value of a multivar in a foreach callback
+ *
+ * The callback will be called on each variable found
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp regular expression to filter which variables we're
+ * interested in. Use NULL to indicate all
+ * @param callback the function to be called on each value of the variable
+ * @param payload opaque pointer to pass to the callback
+ */
+GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload);
+
+/**
+ * Get each value of a multivar
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp regular expression to filter which variables we're
+ * interested in. Use NULL to indicate all
+ */
+GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp);
+
+/**
+ * Return the current entry and advance the iterator
+ *
+ * The pointers returned by this function are valid until the iterator
+ * is freed.
+ *
+ * @param entry pointer to store the entry
+ * @param iter the iterator
+ * @return 0 or an error code. GIT_ITEROVER if the iteration has completed
+ */
+GIT_EXTERN(int) git_config_next(git_config_entry **entry, git_config_iterator *iter);
+
+/**
+ * Free a config iterator
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_config_iterator_free(git_config_iterator *iter);
+
+/**
+ * Set the value of an integer config variable in the config file
+ * with the highest level (usually the local one).
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param value Integer value for the variable
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t value);
+
+/**
+ * Set the value of a long integer config variable in the config file
+ * with the highest level (usually the local one).
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param value Long integer value for the variable
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t value);
+
+/**
+ * Set the value of a boolean config variable in the config file
+ * with the highest level (usually the local one).
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param value the value to store
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value);
+
+/**
+ * Set the value of a string config variable in the config file
+ * with the highest level (usually the local one).
+ *
+ * A copy of the string is made and the user is free to use it
+ * afterwards.
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param value the string to store.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value);
+
+/**
+ * Set a multivar in the local config file.
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp a regular expression to indicate which values to replace
+ * @param value the new value.
+ */
+GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value);
+
+/**
+ * Delete a config variable from the config file
+ * with the highest level (usually the local one).
+ *
+ * @param cfg the configuration
+ * @param name the variable to delete
+ */
+GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name);
+
+/**
+ * Deletes one or several entries from a multivar in the local config file.
+ *
+ * @param cfg where to look for the variables
+ * @param name the variable's name
+ * @param regexp a regular expression to indicate which values to delete
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp);
+
+/**
+ * Perform an operation on each config variable.
+ *
+ * The callback receives the normalized name and value of each variable
+ * in the config backend, and the data pointer passed to this function.
+ * If the callback returns a non-zero value, the function stops iterating
+ * and returns that value to the caller.
+ *
+ * The pointers passed to the callback are only valid as long as the
+ * iteration is ongoing.
+ *
+ * @param cfg where to get the variables from
+ * @param callback the function to call on each variable
+ * @param payload the data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_config_foreach(
+ const git_config *cfg,
+ git_config_foreach_cb callback,
+ void *payload);
+
+/**
+ * Iterate over all the config variables
+ *
+ * Use `git_config_next` to advance the iteration and
+ * `git_config_iterator_free` when done.
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to ge the variables from
+ */
+GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg);
+
+/**
+ * Iterate over all the config variables whose name matches a pattern
+ *
+ * Use `git_config_next` to advance the iteration and
+ * `git_config_iterator_free` when done.
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to ge the variables from
+ * @param regexp regular expression to match the names
+ */
+GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp);
+
+/**
+ * Perform an operation on each config variable matching a regular expression.
+ *
+ * This behaviors like `git_config_foreach` with an additional filter of a
+ * regular expression that filters which config keys are passed to the
+ * callback.
+ *
+ * The pointers passed to the callback are only valid as long as the
+ * iteration is ongoing.
+ *
+ * @param cfg where to get the variables from
+ * @param regexp regular expression to match against config names
+ * @param callback the function to call on each variable
+ * @param payload the data to pass to the callback
+ * @return 0 or the return value of the callback which didn't return 0
+ */
+GIT_EXTERN(int) git_config_foreach_match(
+ const git_config *cfg,
+ const char *regexp,
+ git_config_foreach_cb callback,
+ void *payload);
+
+/**
+ * Query the value of a config variable and return it mapped to
+ * an integer constant.
+ *
+ * This is a helper method to easily map different possible values
+ * to a variable to integer constants that easily identify them.
+ *
+ * A mapping array looks as follows:
+ *
+ * git_cvar_map autocrlf_mapping[] = {
+ * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
+ * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
+ * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT},
+ * {GIT_CVAR_STRING, "default", GIT_AUTO_CRLF_DEFAULT}};
+ *
+ * On any "false" value for the variable (e.g. "false", "FALSE", "no"), the
+ * mapping will store `GIT_AUTO_CRLF_FALSE` in the `out` parameter.
+ *
+ * The same thing applies for any "true" value such as "true", "yes" or "1", storing
+ * the `GIT_AUTO_CRLF_TRUE` variable.
+ *
+ * Otherwise, if the value matches the string "input" (with case insensitive comparison),
+ * the given constant will be stored in `out`, and likewise for "default".
+ *
+ * If not a single match can be made to store in `out`, an error code will be
+ * returned.
+ *
+ * @param out place to store the result of the mapping
+ * @param cfg config file to get the variables from
+ * @param name name of the config variable to lookup
+ * @param maps array of `git_cvar_map` objects specifying the possible mappings
+ * @param map_n number of mapping objects in `maps`
+ * @return 0 on success, error code otherwise
+ */
+GIT_EXTERN(int) git_config_get_mapped(
+ int *out,
+ const git_config *cfg,
+ const char *name,
+ const git_cvar_map *maps,
+ size_t map_n);
+
+/**
+ * Maps a string value to an integer constant
+ *
+ * @param out place to store the result of the parsing
+ * @param maps array of `git_cvar_map` objects specifying the possible mappings
+ * @param map_n number of mapping objects in `maps`
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_lookup_map_value(
+ int *out,
+ const git_cvar_map *maps,
+ size_t map_n,
+ const char *value);
+
+/**
+ * Parse a string value as a bool.
+ *
+ * Valid values for true are: 'true', 'yes', 'on', 1 or any
+ * number different from 0
+ * Valid values for false are: 'false', 'no', 'off', 0
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_bool(int *out, const char *value);
+
+/**
+ * Parse a string value as an int32.
+ *
+ * An optional value suffix of 'k', 'm', or 'g' will
+ * cause the value to be multiplied by 1024, 1048576,
+ * or 1073741824 prior to output.
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value);
+
+/**
+ * Parse a string value as an int64.
+ *
+ * An optional value suffix of 'k', 'm', or 'g' will
+ * cause the value to be multiplied by 1024, 1048576,
+ * or 1073741824 prior to output.
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value);
+
+/**
+ * Parse a string value as a path.
+ *
+ * A leading '~' will be expanded to the global search path (which
+ * defaults to the user's home directory but can be overridden via
+ * `git_libgit2_opts()`.
+ *
+ * If the value does not begin with a tilde, the input will be
+ * returned.
+ *
+ * @param out placae to store the result of parsing
+ * @param value the path to evaluate
+ */
+GIT_EXTERN(int) git_config_parse_path(git_buf *out, const char *value);
+
+/**
+ * Perform an operation on each config variable in given config backend
+ * matching a regular expression.
+ *
+ * This behaviors like `git_config_foreach_match` except instead of all config
+ * entries it just enumerates through the given backend entry.
+ *
+ * @param backend where to get the variables from
+ * @param regexp regular expression to match against config names (can be NULL)
+ * @param callback the function to call on each variable
+ * @param payload the data to pass to the callback
+ */
+GIT_EXTERN(int) git_config_backend_foreach_match(
+ git_config_backend *backend,
+ const char *regexp,
+ git_config_foreach_cb callback,
+ void *payload);
+
+
+/**
+ * Lock the backend with the highest priority
+ *
+ * Locking disallows anybody else from writing to that backend. Any
+ * updates made after locking will not be visible to a reader until
+ * the file is unlocked.
+ *
+ * You can apply the changes by calling `git_transaction_commit()`
+ * before freeing the transaction. Either of these actions will unlock
+ * the config.
+ *
+ * @param tx the resulting transaction, use this to commit or undo the
+ * changes
+ * @param cfg the configuration in which to lock
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_lock(git_transaction **tx, git_config *cfg);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_cred_helpers_h__
+#define INCLUDE_git_cred_helpers_h__
+
+#include "transport.h"
+
+/**
+ * @file git2/cred_helpers.h
+ * @brief Utility functions for credential management
+ * @defgroup git_cred_helpers credential management helpers
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Payload for git_cred_stock_userpass_plaintext.
+ */
+typedef struct git_cred_userpass_payload {
+ const char *username;
+ const char *password;
+} git_cred_userpass_payload;
+
+
+/**
+ * Stock callback usable as a git_cred_acquire_cb. This calls
+ * git_cred_userpass_plaintext_new unless the protocol has not specified
+ * `GIT_CREDTYPE_USERPASS_PLAINTEXT` as an allowed type.
+ *
+ * @param cred The newly created credential object.
+ * @param url The resource for which we are demanding a credential.
+ * @param user_from_url The username that was embedded in a "user\@host"
+ * remote url, or NULL if not included.
+ * @param allowed_types A bitmask stating which cred types are OK to return.
+ * @param payload The payload provided when specifying this callback. (This is
+ * interpreted as a `git_cred_userpass_payload*`.)
+ */
+GIT_EXTERN(int) git_cred_userpass(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload);
+
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_describe_h__
+#define INCLUDE_git_describe_h__
+
+#include "common.h"
+#include "types.h"
+#include "buffer.h"
+
+/**
+ * @file git2/describe.h
+ * @brief Git describing routines
+ * @defgroup git_describe Git describing routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Reference lookup strategy
+ *
+ * These behave like the --tags and --all optios to git-describe,
+ * namely they say to look for any reference in either refs/tags/ or
+ * refs/ respectively.
+ */
+typedef enum {
+ GIT_DESCRIBE_DEFAULT,
+ GIT_DESCRIBE_TAGS,
+ GIT_DESCRIBE_ALL,
+} git_describe_strategy_t;
+
+/**
+ * Describe options structure
+ *
+ * Initialize with `GIT_DESCRIBE_OPTIONS_INIT` macro to correctly set
+ * the `version` field. E.g.
+ *
+ * git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT;
+ */
+typedef struct git_describe_options {
+ unsigned int version;
+
+ unsigned int max_candidates_tags; /**< default: 10 */
+ unsigned int describe_strategy; /**< default: GIT_DESCRIBE_DEFAULT */
+ const char *pattern;
+ /**
+ * When calculating the distance from the matching tag or
+ * reference, only walk down the first-parent ancestry.
+ */
+ int only_follow_first_parent;
+ /**
+ * If no matching tag or reference is found, the describe
+ * operation would normally fail. If this option is set, it
+ * will instead fall back to showing the full id of the
+ * commit.
+ */
+ int show_commit_oid_as_fallback;
+} git_describe_options;
+
+#define GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS 10
+#define GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE 7
+
+#define GIT_DESCRIBE_OPTIONS_VERSION 1
+#define GIT_DESCRIBE_OPTIONS_INIT { \
+ GIT_DESCRIBE_OPTIONS_VERSION, \
+ GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS, \
+}
+
+GIT_EXTERN(int) git_describe_init_options(git_describe_options *opts, unsigned int version);
+
+/**
+ * Options for formatting the describe string
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Size of the abbreviated commit id to use. This value is the
+ * lower bound for the length of the abbreviated string. The
+ * default is 7.
+ */
+ unsigned int abbreviated_size;
+
+ /**
+ * Set to use the long format even when a shorter name could be used.
+ */
+ int always_use_long_format;
+
+ /**
+ * If the workdir is dirty and this is set, this string will
+ * be appended to the description string.
+ */
+ const char *dirty_suffix;
+} git_describe_format_options;
+
+#define GIT_DESCRIBE_FORMAT_OPTIONS_VERSION 1
+#define GIT_DESCRIBE_FORMAT_OPTIONS_INIT { \
+ GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, \
+ GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE, \
+ }
+
+GIT_EXTERN(int) git_describe_init_format_options(git_describe_format_options *opts, unsigned int version);
+
+/**
+ * A struct that stores the result of a describe operation.
+ */
+typedef struct git_describe_result git_describe_result;
+
+/**
+ * Describe a commit
+ *
+ * Perform the describe operation on the given committish object.
+ *
+ * @param result pointer to store the result. You must free this once
+ * you're done with it.
+ * @param committish a committish to describe
+ * @param opts the lookup options
+ */
+GIT_EXTERN(int) git_describe_commit(
+ git_describe_result **result,
+ git_object *committish,
+ git_describe_options *opts);
+
+/**
+ * Describe a commit
+ *
+ * Perform the describe operation on the current commit and the
+ * worktree. After peforming describe on HEAD, a status is run and the
+ * description is considered to be dirty if there are.
+ *
+ * @param out pointer to store the result. You must free this once
+ * you're done with it.
+ * @param repo the repository in which to perform the describe
+ * @param opts the lookup options
+ */
+GIT_EXTERN(int) git_describe_workdir(
+ git_describe_result **out,
+ git_repository *repo,
+ git_describe_options *opts);
+
+/**
+ * Print the describe result to a buffer
+ *
+ * @param out The buffer to store the result
+ * @param result the result from `git_describe_commit()` or
+ * `git_describe_workdir()`.
+ * @param opts the formatting options
+ */
+GIT_EXTERN(int) git_describe_format(
+ git_buf *out,
+ const git_describe_result *result,
+ const git_describe_format_options *opts);
+
+/**
+ * Free the describe result.
+ */
+GIT_EXTERN(void) git_describe_result_free(git_describe_result *result);
+
+/** @} */
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_diff_h__
+#define INCLUDE_git_diff_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "tree.h"
+#include "refs.h"
+
+/**
+ * @file git2/diff.h
+ * @brief Git tree and file differencing routines.
+ *
+ * Overview
+ * --------
+ *
+ * Calculating diffs is generally done in two phases: building a list of
+ * diffs then traversing it. This makes is easier to share logic across
+ * the various types of diffs (tree vs tree, workdir vs index, etc.), and
+ * also allows you to insert optional diff post-processing phases,
+ * such as rename detection, in between the steps. When you are done with
+ * a diff object, it must be freed.
+ *
+ * Terminology
+ * -----------
+ *
+ * To understand the diff APIs, you should know the following terms:
+ *
+ * - A `diff` represents the cumulative list of differences between two
+ * snapshots of a repository (possibly filtered by a set of file name
+ * patterns). This is the `git_diff` object.
+ *
+ * - A `delta` is a file pair with an old and new revision. The old version
+ * may be absent if the file was just created and the new version may be
+ * absent if the file was deleted. A diff is mostly just a list of deltas.
+ *
+ * - A `binary` file / delta is a file (or pair) for which no text diffs
+ * should be generated. A diff can contain delta entries that are
+ * binary, but no diff content will be output for those files. There is
+ * a base heuristic for binary detection and you can further tune the
+ * behavior with git attributes or diff flags and option settings.
+ *
+ * - A `hunk` is a span of modified lines in a delta along with some stable
+ * surrounding context. You can configure the amount of context and other
+ * properties of how hunks are generated. Each hunk also comes with a
+ * header that described where it starts and ends in both the old and new
+ * versions in the delta.
+ *
+ * - A `line` is a range of characters inside a hunk. It could be a context
+ * line (i.e. in both old and new versions), an added line (i.e. only in
+ * the new version), or a removed line (i.e. only in the old version).
+ * Unfortunately, we don't know anything about the encoding of data in the
+ * file being diffed, so we cannot tell you much about the line content.
+ * Line data will not be NUL-byte terminated, however, because it will be
+ * just a span of bytes inside the larger file.
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Flags for diff options. A combination of these flags can be passed
+ * in via the `flags` value in the `git_diff_options`.
+ */
+typedef enum {
+ /** Normal diff, the default */
+ GIT_DIFF_NORMAL = 0,
+
+ /*
+ * Options controlling which files will be in the diff
+ */
+
+ /** Reverse the sides of the diff */
+ GIT_DIFF_REVERSE = (1u << 0),
+
+ /** Include ignored files in the diff */
+ GIT_DIFF_INCLUDE_IGNORED = (1u << 1),
+
+ /** Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory
+ * will be marked with only a single entry in the diff; this flag
+ * adds all files under the directory as IGNORED entries, too.
+ */
+ GIT_DIFF_RECURSE_IGNORED_DIRS = (1u << 2),
+
+ /** Include untracked files in the diff */
+ GIT_DIFF_INCLUDE_UNTRACKED = (1u << 3),
+
+ /** Even with GIT_DIFF_INCLUDE_UNTRACKED, an entire untracked
+ * directory will be marked with only a single entry in the diff
+ * (a la what core Git does in `git status`); this flag adds *all*
+ * files under untracked directories as UNTRACKED entries, too.
+ */
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1u << 4),
+
+ /** Include unmodified files in the diff */
+ GIT_DIFF_INCLUDE_UNMODIFIED = (1u << 5),
+
+ /** Normally, a type change between files will be converted into a
+ * DELETED record for the old and an ADDED record for the new; this
+ * options enabled the generation of TYPECHANGE delta records.
+ */
+ GIT_DIFF_INCLUDE_TYPECHANGE = (1u << 6),
+
+ /** Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still
+ * generally show as a DELETED blob. This flag tries to correctly
+ * label blob->tree transitions as TYPECHANGE records with new_file's
+ * mode set to tree. Note: the tree SHA will not be available.
+ */
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1u << 7),
+
+ /** Ignore file mode changes */
+ GIT_DIFF_IGNORE_FILEMODE = (1u << 8),
+
+ /** Treat all submodules as unmodified */
+ GIT_DIFF_IGNORE_SUBMODULES = (1u << 9),
+
+ /** Use case insensitive filename comparisons */
+ GIT_DIFF_IGNORE_CASE = (1u << 10),
+
+ /** May be combined with `GIT_DIFF_IGNORE_CASE` to specify that a file
+ * that has changed case will be returned as an add/delete pair.
+ */
+ GIT_DIFF_INCLUDE_CASECHANGE = (1u << 11),
+
+ /** If the pathspec is set in the diff options, this flags indicates
+ * that the paths will be treated as literal paths instead of
+ * fnmatch patterns. Each path in the list must either be a full
+ * path to a file or a directory. (A trailing slash indicates that
+ * the path will _only_ match a directory). If a directory is
+ * specified, all children will be included.
+ */
+ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12),
+
+ /** Disable updating of the `binary` flag in delta records. This is
+ * useful when iterating over a diff if you don't need hunk and data
+ * callbacks and want to avoid having to load file completely.
+ */
+ GIT_DIFF_SKIP_BINARY_CHECK = (1u << 13),
+
+ /** When diff finds an untracked directory, to match the behavior of
+ * core Git, it scans the contents for IGNORED and UNTRACKED files.
+ * If *all* contents are IGNORED, then the directory is IGNORED; if
+ * any contents are not IGNORED, then the directory is UNTRACKED.
+ * This is extra work that may not matter in many cases. This flag
+ * turns off that scan and immediately labels an untracked directory
+ * as UNTRACKED (changing the behavior to not match core Git).
+ */
+ GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = (1u << 14),
+
+ /** When diff finds a file in the working directory with stat
+ * information different from the index, but the OID ends up being the
+ * same, write the correct stat information into the index. Note:
+ * without this flag, diff will always leave the index untouched.
+ */
+ GIT_DIFF_UPDATE_INDEX = (1u << 15),
+
+ /** Include unreadable files in the diff */
+ GIT_DIFF_INCLUDE_UNREADABLE = (1u << 16),
+
+ /** Include unreadable files in the diff */
+ GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17),
+
+ /*
+ * Options controlling how output will be generated
+ */
+
+ /** Treat all files as text, disabling binary attributes & detection */
+ GIT_DIFF_FORCE_TEXT = (1u << 20),
+ /** Treat all files as binary, disabling text diffs */
+ GIT_DIFF_FORCE_BINARY = (1u << 21),
+
+ /** Ignore all whitespace */
+ GIT_DIFF_IGNORE_WHITESPACE = (1u << 22),
+ /** Ignore changes in amount of whitespace */
+ GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1u << 23),
+ /** Ignore whitespace at end of line */
+ GIT_DIFF_IGNORE_WHITESPACE_EOL = (1u << 24),
+
+ /** When generating patch text, include the content of untracked
+ * files. This automatically turns on GIT_DIFF_INCLUDE_UNTRACKED but
+ * it does not turn on GIT_DIFF_RECURSE_UNTRACKED_DIRS. Add that
+ * flag if you want the content of every single UNTRACKED file.
+ */
+ GIT_DIFF_SHOW_UNTRACKED_CONTENT = (1u << 25),
+
+ /** When generating output, include the names of unmodified files if
+ * they are included in the git_diff. Normally these are skipped in
+ * the formats that list files (e.g. name-only, name-status, raw).
+ * Even with this, these will not be included in patch format.
+ */
+ GIT_DIFF_SHOW_UNMODIFIED = (1u << 26),
+
+ /** Use the "patience diff" algorithm */
+ GIT_DIFF_PATIENCE = (1u << 28),
+ /** Take extra time to find minimal diff */
+ GIT_DIFF_MINIMAL = (1 << 29),
+
+ /** Include the necessary deflate / delta information so that `git-apply`
+ * can apply given diff information to binary files.
+ */
+ GIT_DIFF_SHOW_BINARY = (1 << 30),
+} git_diff_option_t;
+
+/**
+ * The diff object that contains all individual file deltas.
+ *
+ * This is an opaque structure which will be allocated by one of the diff
+ * generator functions below (such as `git_diff_tree_to_tree`). You are
+ * responsible for releasing the object memory when done, using the
+ * `git_diff_free()` function.
+ */
+typedef struct git_diff git_diff;
+
+/**
+ * Flags for the delta object and the file objects on each side.
+ *
+ * These flags are used for both the `flags` value of the `git_diff_delta`
+ * and the flags for the `git_diff_file` objects representing the old and
+ * new sides of the delta. Values outside of this public range should be
+ * considered reserved for internal or future use.
+ */
+typedef enum {
+ GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */
+ GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */
+ GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */
+ GIT_DIFF_FLAG_EXISTS = (1u << 3), /**< file exists at this side of the delta */
+} git_diff_flag_t;
+
+/**
+ * What type of change is described by a git_diff_delta?
+ *
+ * `GIT_DELTA_RENAMED` and `GIT_DELTA_COPIED` will only show up if you run
+ * `git_diff_find_similar()` on the diff object.
+ *
+ * `GIT_DELTA_TYPECHANGE` only shows up given `GIT_DIFF_INCLUDE_TYPECHANGE`
+ * in the option flags (otherwise type changes will be split into ADDED /
+ * DELETED pairs).
+ */
+typedef enum {
+ GIT_DELTA_UNMODIFIED = 0, /**< no changes */
+ GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */
+ GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */
+ GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */
+ GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */
+ GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */
+ GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */
+ GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */
+ GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */
+ GIT_DELTA_UNREADABLE = 9, /**< entry is unreadable */
+ GIT_DELTA_CONFLICTED = 10, /**< entry in the index is conflicted */
+} git_delta_t;
+
+/**
+ * Description of one side of a delta.
+ *
+ * Although this is called a "file", it could represent a file, a symbolic
+ * link, a submodule commit id, or even a tree (although that only if you
+ * are tracking type changes or ignored/untracked directories).
+ *
+ * The `id` is the `git_oid` of the item. If the entry represents an
+ * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),
+ * then the oid will be zeroes.
+ *
+ * `path` is the NUL-terminated path to the entry relative to the working
+ * directory of the repository.
+ *
+ * `size` is the size of the entry in bytes.
+ *
+ * `flags` is a combination of the `git_diff_flag_t` types
+ *
+ * `mode` is, roughly, the stat() `st_mode` value for the item. This will
+ * be restricted to one of the `git_filemode_t` values.
+ *
+ * The `id_abbrev` represents the known length of the `id` field, when
+ * converted to a hex string. It is generally `GIT_OID_HEXSZ`, unless this
+ * delta was created from reading a patch file, in which case it may be
+ * abbreviated to something reasonable, like 7 characters.
+ */
+typedef struct {
+ git_oid id;
+ const char *path;
+ git_off_t size;
+ uint32_t flags;
+ uint16_t mode;
+ uint16_t id_abbrev;
+} git_diff_file;
+
+/**
+ * Description of changes to one entry.
+ *
+ * When iterating over a diff, this will be passed to most callbacks and
+ * you can use the contents to understand exactly what has changed.
+ *
+ * The `old_file` represents the "from" side of the diff and the `new_file`
+ * represents to "to" side of the diff. What those means depend on the
+ * function that was used to generate the diff and will be documented below.
+ * You can also use the `GIT_DIFF_REVERSE` flag to flip it around.
+ *
+ * Although the two sides of the delta are named "old_file" and "new_file",
+ * they actually may correspond to entries that represent a file, a symbolic
+ * link, a submodule commit id, or even a tree (if you are tracking type
+ * changes or ignored/untracked directories).
+ *
+ * Under some circumstances, in the name of efficiency, not all fields will
+ * be filled in, but we generally try to fill in as much as possible. One
+ * example is that the "flags" field may not have either the `BINARY` or the
+ * `NOT_BINARY` flag set to avoid examining file contents if you do not pass
+ * in hunk and/or line callbacks to the diff foreach iteration function. It
+ * will just use the git attributes for those files.
+ *
+ * The similarity score is zero unless you call `git_diff_find_similar()`
+ * which does a similarity analysis of files in the diff. Use that
+ * function to do rename and copy detection, and to split heavily modified
+ * files in add/delete pairs. After that call, deltas with a status of
+ * GIT_DELTA_RENAMED or GIT_DELTA_COPIED will have a similarity score
+ * between 0 and 100 indicating how similar the old and new sides are.
+ *
+ * If you ask `git_diff_find_similar` to find heavily modified files to
+ * break, but to not *actually* break the records, then GIT_DELTA_MODIFIED
+ * records may have a non-zero similarity score if the self-similarity is
+ * below the split threshold. To display this value like core Git, invert
+ * the score (a la `printf("M%03d", 100 - delta->similarity)`).
+ */
+typedef struct {
+ git_delta_t status;
+ uint32_t flags; /**< git_diff_flag_t values */
+ uint16_t similarity; /**< for RENAMED and COPIED, value 0-100 */
+ uint16_t nfiles; /**< number of files in this delta */
+ git_diff_file old_file;
+ git_diff_file new_file;
+} git_diff_delta;
+
+/**
+ * Diff notification callback function.
+ *
+ * The callback will be called for each file, just before the `git_delta_t`
+ * gets inserted into the diff.
+ *
+ * When the callback:
+ * - returns < 0, the diff process will be aborted.
+ * - returns > 0, the delta will not be inserted into the diff, but the
+ * diff process continues.
+ * - returns 0, the delta is inserted into the diff, and the diff process
+ * continues.
+ */
+typedef int (*git_diff_notify_cb)(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload);
+
+/**
+ * Diff progress callback.
+ *
+ * Called before each file comparison.
+ *
+ * @param diff_so_far The diff being generated.
+ * @param old_path The path to the old file or NULL.
+ * @param new_path The path to the new file or NULL.
+ * @return Non-zero to abort the diff.
+ */
+typedef int (*git_diff_progress_cb)(
+ const git_diff *diff_so_far,
+ const char *old_path,
+ const char *new_path,
+ void *payload);
+
+/**
+ * Structure describing options about how the diff should be executed.
+ *
+ * Setting all values of the structure to zero will yield the default
+ * values. Similarly, passing NULL for the options structure will
+ * give the defaults. The default values are marked below.
+ *
+ * - `flags` is a combination of the `git_diff_option_t` values above
+ * - `context_lines` is the number of unchanged lines that define the
+ * boundary of a hunk (and to display before and after)
+ * - `interhunk_lines` is the maximum number of unchanged lines between
+ * hunk boundaries before the hunks will be merged into a one.
+ * - `old_prefix` is the virtual "directory" to prefix to old file names
+ * in hunk headers (default "a")
+ * - `new_prefix` is the virtual "directory" to prefix to new file names
+ * in hunk headers (default "b")
+ * - `pathspec` is an array of paths / fnmatch patterns to constrain diff
+ * - `max_size` is a file size (in bytes) above which a blob will be marked
+ * as binary automatically; pass a negative value to disable.
+ * - `notify_cb` is an optional callback function, notifying the consumer of
+ * changes to the diff as new deltas are added.
+ * - `progress_cb` is an optional callback function, notifying the consumer of
+ * which files are being examined as the diff is generated.
+ * - `payload` is the payload to pass to the callback functions.
+ * - `ignore_submodules` overrides the submodule ignore setting for all
+ * submodules in the diff.
+ */
+typedef struct {
+ unsigned int version; /**< version for the struct */
+ uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */
+
+ /* options controlling which files are in the diff */
+
+ git_submodule_ignore_t ignore_submodules; /**< submodule ignore rule */
+ git_strarray pathspec; /**< defaults to include all paths */
+ git_diff_notify_cb notify_cb;
+ git_diff_progress_cb progress_cb;
+ void *payload;
+
+ /* options controlling how to diff text is generated */
+
+ uint32_t context_lines; /**< defaults to 3 */
+ uint32_t interhunk_lines; /**< defaults to 0 */
+ uint16_t id_abbrev; /**< default 'core.abbrev' or 7 if unset */
+ git_off_t max_size; /**< defaults to 512MB */
+ const char *old_prefix; /**< defaults to "a" */
+ const char *new_prefix; /**< defaults to "b" */
+} git_diff_options;
+
+/* The current version of the diff options structure */
+#define GIT_DIFF_OPTIONS_VERSION 1
+
+/* Stack initializer for diff options. Alternatively use
+ * `git_diff_options_init` programmatic initialization.
+ */
+#define GIT_DIFF_OPTIONS_INIT \
+ {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL,0}, NULL, NULL, NULL, 3}
+
+/**
+ * Initializes a `git_diff_options` with default values. Equivalent to
+ * creating an instance with GIT_DIFF_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_init_options(
+ git_diff_options *opts,
+ unsigned int version);
+
+/**
+ * When iterating over a diff, callback that will be made per file.
+ *
+ * @param delta A pointer to the delta data for the file
+ * @param progress Goes from 0 to 1 over the diff
+ * @param payload User-specified pointer from foreach function
+ */
+typedef int (*git_diff_file_cb)(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload);
+
+#define GIT_DIFF_HUNK_HEADER_SIZE 128
+
+/**
+ * When producing a binary diff, the binary data returned will be
+ * either the deflated full ("literal") contents of the file, or
+ * the deflated binary delta between the two sides (whichever is
+ * smaller).
+ */
+typedef enum {
+ /** There is no binary delta. */
+ GIT_DIFF_BINARY_NONE,
+
+ /** The binary data is the literal contents of the file. */
+ GIT_DIFF_BINARY_LITERAL,
+
+ /** The binary data is the delta from one side to the other. */
+ GIT_DIFF_BINARY_DELTA,
+} git_diff_binary_t;
+
+/** The contents of one of the files in a binary diff. */
+typedef struct {
+ /** The type of binary data for this file. */
+ git_diff_binary_t type;
+
+ /** The binary data, deflated. */
+ const char *data;
+
+ /** The length of the binary data. */
+ size_t datalen;
+
+ /** The length of the binary data after inflation. */
+ size_t inflatedlen;
+} git_diff_binary_file;
+
+/** Structure describing the binary contents of a diff. */
+typedef struct {
+ /**
+ * Whether there is data in this binary structure or not. If this
+ * is `1`, then this was produced and included binary content. If
+ * this is `0` then this was generated knowing only that a binary
+ * file changed but without providing the data, probably from a patch
+ * that said `Binary files a/file.txt and b/file.txt differ`.
+ */
+ unsigned int contains_data;
+ git_diff_binary_file old_file; /**< The contents of the old file. */
+ git_diff_binary_file new_file; /**< The contents of the new file. */
+} git_diff_binary;
+
+/**
+* When iterating over a diff, callback that will be made for
+* binary content within the diff.
+*/
+typedef int(*git_diff_binary_cb)(
+ const git_diff_delta *delta,
+ const git_diff_binary *binary,
+ void *payload);
+
+/**
+ * Structure describing a hunk of a diff.
+ */
+typedef struct {
+ int old_start; /** Starting line number in old_file */
+ int old_lines; /** Number of lines in old_file */
+ int new_start; /** Starting line number in new_file */
+ int new_lines; /** Number of lines in new_file */
+ size_t header_len; /** Number of bytes in header text */
+ char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */
+} git_diff_hunk;
+
+/**
+ * When iterating over a diff, callback that will be made per hunk.
+ */
+typedef int (*git_diff_hunk_cb)(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *payload);
+
+/**
+ * Line origin constants.
+ *
+ * These values describe where a line came from and will be passed to
+ * the git_diff_line_cb when iterating over a diff. There are some
+ * special origin constants at the end that are used for the text
+ * output callbacks to demarcate lines that are actually part of
+ * the file or hunk headers.
+ */
+typedef enum {
+ /* These values will be sent to `git_diff_line_cb` along with the line */
+ GIT_DIFF_LINE_CONTEXT = ' ',
+ GIT_DIFF_LINE_ADDITION = '+',
+ GIT_DIFF_LINE_DELETION = '-',
+
+ GIT_DIFF_LINE_CONTEXT_EOFNL = '=', /**< Both files have no LF at end */
+ GIT_DIFF_LINE_ADD_EOFNL = '>', /**< Old has no LF at end, new does */
+ GIT_DIFF_LINE_DEL_EOFNL = '<', /**< Old has LF at end, new does not */
+
+ /* The following values will only be sent to a `git_diff_line_cb` when
+ * the content of a diff is being formatted through `git_diff_print`.
+ */
+ GIT_DIFF_LINE_FILE_HDR = 'F',
+ GIT_DIFF_LINE_HUNK_HDR = 'H',
+ GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */
+} git_diff_line_t;
+
+/**
+ * Structure describing a line (or data span) of a diff.
+ */
+typedef struct {
+ char origin; /**< A git_diff_line_t value */
+ int old_lineno; /**< Line number in old file or -1 for added line */
+ int new_lineno; /**< Line number in new file or -1 for deleted line */
+ int num_lines; /**< Number of newline characters in content */
+ size_t content_len; /**< Number of bytes of data */
+ git_off_t content_offset; /**< Offset in the original file to the content */
+ const char *content; /**< Pointer to diff text, not NUL-byte terminated */
+} git_diff_line;
+
+/**
+ * When iterating over a diff, callback that will be made per text diff
+ * line. In this context, the provided range will be NULL.
+ *
+ * When printing a diff, callback that will be made to output each line
+ * of text. This uses some extra GIT_DIFF_LINE_... constants for output
+ * of lines of file and hunk headers.
+ */
+typedef int (*git_diff_line_cb)(
+ const git_diff_delta *delta, /**< delta that contains this data */
+ const git_diff_hunk *hunk, /**< hunk containing this data */
+ const git_diff_line *line, /**< line data */
+ void *payload); /**< user reference data */
+
+/**
+ * Flags to control the behavior of diff rename/copy detection.
+ */
+typedef enum {
+ /** Obey `diff.renames`. Overridden by any other GIT_DIFF_FIND_... flag. */
+ GIT_DIFF_FIND_BY_CONFIG = 0,
+
+ /** Look for renames? (`--find-renames`) */
+ GIT_DIFF_FIND_RENAMES = (1u << 0),
+
+ /** Consider old side of MODIFIED for renames? (`--break-rewrites=N`) */
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1u << 1),
+
+ /** Look for copies? (a la `--find-copies`). */
+ GIT_DIFF_FIND_COPIES = (1u << 2),
+
+ /** Consider UNMODIFIED as copy sources? (`--find-copies-harder`).
+ *
+ * For this to work correctly, use GIT_DIFF_INCLUDE_UNMODIFIED when
+ * the initial `git_diff` is being generated.
+ */
+ GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1u << 3),
+
+ /** Mark significant rewrites for split (`--break-rewrites=/M`) */
+ GIT_DIFF_FIND_REWRITES = (1u << 4),
+ /** Actually split large rewrites into delete/add pairs */
+ GIT_DIFF_BREAK_REWRITES = (1u << 5),
+ /** Mark rewrites for split and break into delete/add pairs */
+ GIT_DIFF_FIND_AND_BREAK_REWRITES =
+ (GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES),
+
+ /** Find renames/copies for UNTRACKED items in working directory.
+ *
+ * For this to work correctly, use GIT_DIFF_INCLUDE_UNTRACKED when the
+ * initial `git_diff` is being generated (and obviously the diff must
+ * be against the working directory for this to make sense).
+ */
+ GIT_DIFF_FIND_FOR_UNTRACKED = (1u << 6),
+
+ /** Turn on all finding features. */
+ GIT_DIFF_FIND_ALL = (0x0ff),
+
+ /** Measure similarity ignoring leading whitespace (default) */
+ GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,
+ /** Measure similarity ignoring all whitespace */
+ GIT_DIFF_FIND_IGNORE_WHITESPACE = (1u << 12),
+ /** Measure similarity including all data */
+ GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1u << 13),
+ /** Measure similarity only by comparing SHAs (fast and cheap) */
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1u << 14),
+
+ /** Do not break rewrites unless they contribute to a rename.
+ *
+ * Normally, GIT_DIFF_FIND_AND_BREAK_REWRITES will measure the self-
+ * similarity of modified files and split the ones that have changed a
+ * lot into a DELETE / ADD pair. Then the sides of that pair will be
+ * considered candidates for rename and copy detection.
+ *
+ * If you add this flag in and the split pair is *not* used for an
+ * actual rename or copy, then the modified record will be restored to
+ * a regular MODIFIED record instead of being split.
+ */
+ GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1u << 15),
+
+ /** Remove any UNMODIFIED deltas after find_similar is done.
+ *
+ * Using GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED to emulate the
+ * --find-copies-harder behavior requires building a diff with the
+ * GIT_DIFF_INCLUDE_UNMODIFIED flag. If you do not want UNMODIFIED
+ * records in the final result, pass this flag to have them removed.
+ */
+ GIT_DIFF_FIND_REMOVE_UNMODIFIED = (1u << 16),
+} git_diff_find_t;
+
+/**
+ * Pluggable similarity metric
+ */
+typedef struct {
+ int (*file_signature)(
+ void **out, const git_diff_file *file,
+ const char *fullpath, void *payload);
+ int (*buffer_signature)(
+ void **out, const git_diff_file *file,
+ const char *buf, size_t buflen, void *payload);
+ void (*free_signature)(void *sig, void *payload);
+ int (*similarity)(int *score, void *siga, void *sigb, void *payload);
+ void *payload;
+} git_diff_similarity_metric;
+
+/**
+ * Control behavior of rename and copy detection
+ *
+ * These options mostly mimic parameters that can be passed to git-diff.
+ *
+ * - `rename_threshold` is the same as the -M option with a value
+ * - `copy_threshold` is the same as the -C option with a value
+ * - `rename_from_rewrite_threshold` matches the top of the -B option
+ * - `break_rewrite_threshold` matches the bottom of the -B option
+ * - `rename_limit` is the maximum number of matches to consider for
+ * a particular file. This is a little different from the `-l` option
+ * to regular Git because we will still process up to this many matches
+ * before abandoning the search.
+ *
+ * The `metric` option allows you to plug in a custom similarity metric.
+ * Set it to NULL for the default internal metric which is based on sampling
+ * hashes of ranges of data in the file. The default metric is a pretty
+ * good similarity approximation that should work fairly well for both text
+ * and binary data, and is pretty fast with fixed memory overhead.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Combination of git_diff_find_t values (default GIT_DIFF_FIND_BY_CONFIG).
+ * NOTE: if you don't explicitly set this, `diff.renames` could be set
+ * to false, resulting in `git_diff_find_similar` doing nothing.
+ */
+ uint32_t flags;
+
+ /** Similarity to consider a file renamed (default 50) */
+ uint16_t rename_threshold;
+ /** Similarity of modified to be eligible rename source (default 50) */
+ uint16_t rename_from_rewrite_threshold;
+ /** Similarity to consider a file a copy (default 50) */
+ uint16_t copy_threshold;
+ /** Similarity to split modify into delete/add pair (default 60) */
+ uint16_t break_rewrite_threshold;
+
+ /** Maximum similarity sources to examine for a file (somewhat like
+ * git-diff's `-l` option or `diff.renameLimit` config) (default 200)
+ */
+ size_t rename_limit;
+
+ /** Pluggable similarity metric; pass NULL to use internal metric */
+ git_diff_similarity_metric *metric;
+} git_diff_find_options;
+
+#define GIT_DIFF_FIND_OPTIONS_VERSION 1
+#define GIT_DIFF_FIND_OPTIONS_INIT {GIT_DIFF_FIND_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_diff_find_options` with default values. Equivalent to
+ * creating an instance with GIT_DIFF_FIND_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_find_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_FIND_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_find_init_options(
+ git_diff_find_options *opts,
+ unsigned int version);
+
+/** @name Diff Generator Functions
+ *
+ * These are the functions you would use to create (or destroy) a
+ * git_diff from various objects in a repository.
+ */
+/**@{*/
+
+/**
+ * Deallocate a diff.
+ *
+ * @param diff The previously created diff; cannot be used after free.
+ */
+GIT_EXTERN(void) git_diff_free(git_diff *diff);
+
+/**
+ * Create a diff with the difference between two tree objects.
+ *
+ * This is equivalent to `git diff <old-tree> <new-tree>`
+ *
+ * The first tree will be used for the "old_file" side of the delta and the
+ * second tree will be used for the "new_file" side of the delta. You can
+ * pass NULL to indicate an empty tree, although it is an error to pass
+ * NULL for both the `old_tree` and `new_tree`.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository containing the trees.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param new_tree A git_tree object to diff to, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_tree(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_tree *new_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and repository index.
+ *
+ * This is equivalent to `git diff --cached <treeish>` or if you pass
+ * the HEAD tree, then like `git diff --cached`.
+ *
+ * The tree you pass will be used for the "old_file" side of the delta, and
+ * the index will be used for the "new_file" side of the delta.
+ *
+ * If you pass NULL for the index, then the existing index of the `repo`
+ * will be used. In this case, the index will be refreshed from disk
+ * (if it has changed) before the diff is generated.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository containing the tree and index.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param index The index to diff with; repo index used if NULL.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between the repository index and the workdir directory.
+ *
+ * This matches the `git diff` command. See the note below on
+ * `git_diff_tree_to_workdir` for a discussion of the difference between
+ * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
+ * using libgit2.
+ *
+ * The index will be used for the "old_file" side of the delta, and the
+ * working directory will be used for the "new_file" side of the delta.
+ *
+ * If you pass NULL for the index, then the existing index of the `repo`
+ * will be used. In this case, the index will be refreshed from disk
+ * (if it has changed) before the diff is generated.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository.
+ * @param index The index to diff from; repo index used if NULL.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_index_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and the working directory.
+ *
+ * The tree you provide will be used for the "old_file" side of the delta,
+ * and the working directory will be used for the "new_file" side.
+ *
+ * This is not the same as `git diff <treeish>` or `git diff-index
+ * <treeish>`. Those commands use information from the index, whereas this
+ * function strictly returns the differences between the tree and the files
+ * in the working directory, regardless of the state of the index. Use
+ * `git_diff_tree_to_workdir_with_index` to emulate those commands.
+ *
+ * To see difference between this and `git_diff_tree_to_workdir_with_index`,
+ * consider the example of a staged file deletion where the file has then
+ * been put back into the working dir and further modified. The
+ * tree-to-workdir diff for that file is 'modified', but `git diff` would
+ * show status 'deleted' since there is a staged delete.
+ *
+ * @param diff A pointer to a git_diff pointer that will be allocated.
+ * @param repo The repository containing the tree.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and the working directory using index data
+ * to account for staged deletes, tracked files, etc.
+ *
+ * This emulates `git diff <tree>` by diffing the tree to the index and
+ * the index to the working directory and blending the results into a
+ * single diff that includes staged deleted, etc.
+ *
+ * @param diff A pointer to a git_diff pointer that will be allocated.
+ * @param repo The repository containing the tree.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_workdir_with_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff with the difference between two index objects.
+ *
+ * The first index will be used for the "old_file" side of the delta and the
+ * second index will be used for the "new_file" side of the delta.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository containing the indexes.
+ * @param old_index A git_index object to diff from.
+ * @param new_index A git_index object to diff to.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_index_to_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_index *old_index,
+ git_index *new_index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Merge one diff into another.
+ *
+ * This merges items from the "from" list into the "onto" list. The
+ * resulting diff will have all items that appear in either list.
+ * If an item appears in both lists, then it will be "merged" to appear
+ * as if the old version was from the "onto" list and the new version
+ * is from the "from" list (with the exception that if the item has a
+ * pending DELETE in the middle, then it will show as deleted).
+ *
+ * @param onto Diff to merge into.
+ * @param from Diff to merge.
+ */
+GIT_EXTERN(int) git_diff_merge(
+ git_diff *onto,
+ const git_diff *from);
+
+/**
+ * Transform a diff marking file renames, copies, etc.
+ *
+ * This modifies a diff in place, replacing old entries that look
+ * like renames or copies with new entries reflecting those changes.
+ * This also will, if requested, break modified files into add/remove
+ * pairs if the amount of change is above a threshold.
+ *
+ * @param diff diff to run detection algorithms on
+ * @param options Control how detection should be run, NULL for defaults
+ * @return 0 on success, -1 on failure
+ */
+GIT_EXTERN(int) git_diff_find_similar(
+ git_diff *diff,
+ const git_diff_find_options *options);
+
+/**@}*/
+
+
+/** @name Diff Processor Functions
+ *
+ * These are the functions you apply to a diff to process it
+ * or read it in some way.
+ */
+/**@{*/
+
+/**
+ * Query how many diff records are there in a diff.
+ *
+ * @param diff A git_diff generated by one of the above functions
+ * @return Count of number of deltas in the list
+ */
+GIT_EXTERN(size_t) git_diff_num_deltas(const git_diff *diff);
+
+/**
+ * Query how many diff deltas are there in a diff filtered by type.
+ *
+ * This works just like `git_diff_entrycount()` with an extra parameter
+ * that is a `git_delta_t` and returns just the count of how many deltas
+ * match that particular type.
+ *
+ * @param diff A git_diff generated by one of the above functions
+ * @param type A git_delta_t value to filter the count
+ * @return Count of number of deltas matching delta_t type
+ */
+GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
+ const git_diff *diff, git_delta_t type);
+
+/**
+ * Return the diff delta for an entry in the diff list.
+ *
+ * The `git_diff_delta` pointer points to internal data and you do not
+ * have to release it when you are done with it. It will go away when
+ * the * `git_diff` (or any associated `git_patch`) goes away.
+ *
+ * Note that the flags on the delta related to whether it has binary
+ * content or not may not be set if there are no attributes set for the
+ * file and there has been no reason to load the file data at this point.
+ * For now, if you need those flags to be up to date, your only option is
+ * to either use `git_diff_foreach` or create a `git_patch`.
+ *
+ * @param diff Diff list object
+ * @param idx Index into diff list
+ * @return Pointer to git_diff_delta (or NULL if `idx` out of range)
+ */
+GIT_EXTERN(const git_diff_delta *) git_diff_get_delta(
+ const git_diff *diff, size_t idx);
+
+/**
+ * Check if deltas are sorted case sensitively or insensitively.
+ *
+ * @param diff diff to check
+ * @return 0 if case sensitive, 1 if case is ignored
+ */
+GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff *diff);
+
+/**
+ * Loop over all deltas in a diff issuing callbacks.
+ *
+ * This will iterate through all of the files described in a diff. You
+ * should provide a file callback to learn about each file.
+ *
+ * The "hunk" and "line" callbacks are optional, and the text diff of the
+ * files will only be calculated if they are not NULL. Of course, these
+ * callbacks will not be invoked for binary files on the diff or for
+ * files whose only changed is a file mode change.
+ *
+ * Returning a non-zero value from any of the callbacks will terminate
+ * the iteration and return the value to the user.
+ *
+ * @param diff A git_diff generated by one of the above functions.
+ * @param file_cb Callback function to make per file in the diff.
+ * @param binary_cb Optional callback to make for binary files.
+ * @param hunk_cb Optional callback to make per hunk of text diff. This
+ * callback is called to describe a range of lines in the
+ * diff. It will not be issued for binary files.
+ * @param line_cb Optional callback to make per line of diff text. This
+ * same callback will be made for context lines, added, and
+ * removed lines, and even for a deleted trailing newline.
+ * @param payload Reference pointer that will be passed to your callbacks.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_foreach(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Look up the single character abbreviation for a delta status code.
+ *
+ * When you run `git diff --name-status` it uses single letter codes in
+ * the output such as 'A' for added, 'D' for deleted, 'M' for modified,
+ * etc. This function converts a git_delta_t value into these letters for
+ * your own purposes. GIT_DELTA_UNTRACKED will return a space (i.e. ' ').
+ *
+ * @param status The git_delta_t value to look up
+ * @return The single character label for that code
+ */
+GIT_EXTERN(char) git_diff_status_char(git_delta_t status);
+
+/**
+ * Possible output formats for diff data
+ */
+typedef enum {
+ GIT_DIFF_FORMAT_PATCH = 1u, /**< full git diff */
+ GIT_DIFF_FORMAT_PATCH_HEADER = 2u, /**< just the file headers of patch */
+ GIT_DIFF_FORMAT_RAW = 3u, /**< like git diff --raw */
+ GIT_DIFF_FORMAT_NAME_ONLY = 4u, /**< like git diff --name-only */
+ GIT_DIFF_FORMAT_NAME_STATUS = 5u, /**< like git diff --name-status */
+} git_diff_format_t;
+
+/**
+ * Iterate over a diff generating formatted text output.
+ *
+ * Returning a non-zero value from the callbacks will terminate the
+ * iteration and return the non-zero value to the caller.
+ *
+ * @param diff A git_diff generated by one of the above functions.
+ * @param format A git_diff_format_t value to pick the text format.
+ * @param print_cb Callback to make per line of diff text.
+ * @param payload Reference pointer that will be passed to your callback.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_print(
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb print_cb,
+ void *payload);
+
+/**
+ * Produce the complete formatted text output from a diff into a
+ * buffer.
+ *
+ * @param out A pointer to a user-allocated git_buf that will
+ * contain the diff text
+ * @param diff A git_diff generated by one of the above functions.
+ * @param format A git_diff_format_t value to pick the text format.
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_diff_to_buf(
+ git_buf *out,
+ git_diff *diff,
+ git_diff_format_t format);
+
+/**@}*/
+
+
+/*
+ * Misc
+ */
+
+/**
+ * Directly run a diff on two blobs.
+ *
+ * Compared to a file, a blob lacks some contextual information. As such,
+ * the `git_diff_file` given to the callback will have some fake data; i.e.
+ * `mode` will be 0 and `path` will be NULL.
+ *
+ * NULL is allowed for either `old_blob` or `new_blob` and will be treated
+ * as an empty blob, with the `oid` set to NULL in the `git_diff_file` data.
+ * Passing NULL for both blobs is a noop; no callbacks will be made at all.
+ *
+ * We do run a binary content check on the blob content and if either blob
+ * looks like binary data, the `git_diff_delta` binary attribute will be set
+ * to 1 and no call to the hunk_cb nor line_cb will be made (unless you pass
+ * `GIT_DIFF_FORCE_TEXT` of course).
+ *
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param new_blob Blob for new side of diff, or NULL for empty blob
+ * @param new_as_path Treat new blob as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param binary_cb Callback for binary files; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_blobs(
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const git_blob *new_blob,
+ const char *new_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Directly run a diff between a blob and a buffer.
+ *
+ * As with `git_diff_blobs`, comparing a blob and buffer lacks some context,
+ * so the `git_diff_file` parameters to the callbacks will be faked a la the
+ * rules for `git_diff_blobs()`.
+ *
+ * Passing NULL for `old_blob` will be treated as an empty blob (i.e. the
+ * `file_cb` will be invoked with GIT_DELTA_ADDED and the diff will be the
+ * entire content of the buffer added). Passing NULL to the buffer will do
+ * the reverse, with GIT_DELTA_REMOVED and blob content removed.
+ *
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param buffer Raw data for new side of diff, or NULL for empty
+ * @param buffer_len Length of raw data for new side of diff
+ * @param buffer_as_path Treat buffer as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param binary_cb Callback for binary files; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const char *buffer,
+ size_t buffer_len,
+ const char *buffer_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Directly run a diff between two buffers.
+ *
+ * Even more than with `git_diff_blobs`, comparing two buffer lacks
+ * context, so the `git_diff_file` parameters to the callbacks will be
+ * faked a la the rules for `git_diff_blobs()`.
+ *
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param binary_cb Callback for binary files; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_buffers(
+ const void *old_buffer,
+ size_t old_len,
+ const char *old_as_path,
+ const void *new_buffer,
+ size_t new_len,
+ const char *new_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Read the contents of a git patch file into a `git_diff` object.
+ *
+ * The diff object produced is similar to the one that would be
+ * produced if you actually produced it computationally by comparing
+ * two trees, however there may be subtle differences. For example,
+ * a patch file likely contains abbreviated object IDs, so the
+ * object IDs in a `git_diff_delta` produced by this function will
+ * also be abbreviated.
+ *
+ * This function will only read patch files created by a git
+ * implementation, it will not read unified diffs produced by
+ * the `diff` program, nor any other types of patch files.
+ *
+ * @param out A pointer to a git_diff pointer that will be allocated.
+ * @param content The contents of a patch file
+ * @param content_len The length of the patch file contents
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_diff_from_buffer(
+ git_diff **out,
+ const char *content,
+ size_t content_len);
+
+/**
+ * This is an opaque structure which is allocated by `git_diff_get_stats`.
+ * You are responsible for releasing the object memory when done, using the
+ * `git_diff_stats_free()` function.
+ */
+typedef struct git_diff_stats git_diff_stats;
+
+/**
+ * Formatting options for diff stats
+ */
+typedef enum {
+ /** No stats*/
+ GIT_DIFF_STATS_NONE = 0,
+
+ /** Full statistics, equivalent of `--stat` */
+ GIT_DIFF_STATS_FULL = (1u << 0),
+
+ /** Short statistics, equivalent of `--shortstat` */
+ GIT_DIFF_STATS_SHORT = (1u << 1),
+
+ /** Number statistics, equivalent of `--numstat` */
+ GIT_DIFF_STATS_NUMBER = (1u << 2),
+
+ /** Extended header information such as creations, renames and mode changes, equivalent of `--summary` */
+ GIT_DIFF_STATS_INCLUDE_SUMMARY = (1u << 3),
+} git_diff_stats_format_t;
+
+/**
+ * Accumulate diff statistics for all patches.
+ *
+ * @param out Structure containg the diff statistics.
+ * @param diff A git_diff generated by one of the above functions.
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_get_stats(
+ git_diff_stats **out,
+ git_diff *diff);
+
+/**
+ * Get the total number of files changed in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of files changed in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_files_changed(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of insertions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of insertions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_insertions(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of deletions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of deletions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_deletions(
+ const git_diff_stats *stats);
+
+/**
+ * Print diff statistics to a `git_buf`.
+ *
+ * @param out buffer to store the formatted diff statistics in.
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @param format Formatting option.
+ * @param width Target width for output (only affects GIT_DIFF_STATS_FULL)
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format,
+ size_t width);
+
+/**
+ * Deallocate a `git_diff_stats`.
+ *
+ * @param stats The previously created statistics object;
+ * cannot be used after free.
+ */
+GIT_EXTERN(void) git_diff_stats_free(git_diff_stats *stats);
+
+/**
+ * Formatting options for diff e-mail generation
+ */
+typedef enum {
+ /** Normal patch, the default */
+ GIT_DIFF_FORMAT_EMAIL_NONE = 0,
+
+ /** Don't insert "[PATCH]" in the subject header*/
+ GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER = (1 << 0),
+
+} git_diff_format_email_flags_t;
+
+/**
+ * Options for controlling the formatting of the generated e-mail.
+ */
+typedef struct {
+ unsigned int version;
+
+ git_diff_format_email_flags_t flags;
+
+ /** This patch number */
+ size_t patch_no;
+
+ /** Total number of patches in this series */
+ size_t total_patches;
+
+ /** id to use for the commit */
+ const git_oid *id;
+
+ /** Summary of the change */
+ const char *summary;
+
+ /** Commit message's body */
+ const char *body;
+
+ /** Author of the change */
+ const git_signature *author;
+} git_diff_format_email_options;
+
+#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION 1
+#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL, NULL}
+
+/**
+ * Create an e-mail ready patch from a diff.
+ *
+ * @param out buffer to store the e-mail patch in
+ * @param diff containing the commit
+ * @param opts structure with options to influence content and formatting.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_diff_format_email(
+ git_buf *out,
+ git_diff *diff,
+ const git_diff_format_email_options *opts);
+
+/**
+ * Create an e-mail ready patch for a commit.
+ *
+ * Does not support creating patches for merge commits (yet).
+ *
+ * @param out buffer to store the e-mail patch in
+ * @param repo containing the commit
+ * @param commit pointer to up commit
+ * @param patch_no patch number of the commit
+ * @param total_patches total number of patches in the patch set
+ * @param flags determines the formatting of the e-mail
+ * @param diff_opts structure with options to influence diff or NULL for defaults.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_diff_commit_as_email(
+ git_buf *out,
+ git_repository *repo,
+ git_commit *commit,
+ size_t patch_no,
+ size_t total_patches,
+ git_diff_format_email_flags_t flags,
+ const git_diff_options *diff_opts);
+
+/**
+ * Initializes a `git_diff_format_email_options` with default values.
+ *
+ * Equivalent to creating an instance with GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_format_email_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_format_email_init_options(
+ git_diff_format_email_options *opts,
+ unsigned int version);
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_errors_h__
+#define INCLUDE_git_errors_h__
+
+#include "common.h"
+
+/**
+ * @file git2/errors.h
+ * @brief Git error handling routines and variables
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Generic return codes */
+typedef enum {
+ GIT_OK = 0, /**< No error */
+
+ GIT_ERROR = -1, /**< Generic error */
+ GIT_ENOTFOUND = -3, /**< Requested object could not be found */
+ GIT_EEXISTS = -4, /**< Object exists preventing operation */
+ GIT_EAMBIGUOUS = -5, /**< More than one object matches */
+ GIT_EBUFS = -6, /**< Output buffer too short to hold data */
+
+ /* GIT_EUSER is a special error that is never generated by libgit2
+ * code. You can return it from a callback (e.g to stop an iteration)
+ * to know that it was generated by the callback and not by libgit2.
+ */
+ GIT_EUSER = -7,
+
+ GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */
+ GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */
+ GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */
+ GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */
+ GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */
+ GIT_ECONFLICT = -13, /**< Checkout conflicts prevented operation */
+ GIT_ELOCKED = -14, /**< Lock file prevented operation */
+ GIT_EMODIFIED = -15, /**< Reference value does not match expected */
+ GIT_EAUTH = -16, /**< Authentication error */
+ GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */
+ GIT_EAPPLIED = -18, /**< Patch/merge has already been applied */
+ GIT_EPEEL = -19, /**< The requested peel operation is not possible */
+ GIT_EEOF = -20, /**< Unexpected EOF */
+ GIT_EINVALID = -21, /**< Invalid operation or input */
+ GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */
+ GIT_EDIRECTORY = -23, /**< The operation is not valid for a directory */
+ GIT_EMERGECONFLICT = -24, /**< A merge conflict exists and cannot continue */
+
+ GIT_PASSTHROUGH = -30, /**< Internal only */
+ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
+} git_error_code;
+
+/**
+ * Structure to store extra details of the last error that occurred.
+ *
+ * This is kept on a per-thread basis if GIT_THREADS was defined when the
+ * library was build, otherwise one is kept globally for the library
+ */
+typedef struct {
+ char *message;
+ int klass;
+} git_error;
+
+/** Error classes */
+typedef enum {
+ GITERR_NONE = 0,
+ GITERR_NOMEMORY,
+ GITERR_OS,
+ GITERR_INVALID,
+ GITERR_REFERENCE,
+ GITERR_ZLIB,
+ GITERR_REPOSITORY,
+ GITERR_CONFIG,
+ GITERR_REGEX,
+ GITERR_ODB,
+ GITERR_INDEX,
+ GITERR_OBJECT,
+ GITERR_NET,
+ GITERR_TAG,
+ GITERR_TREE,
+ GITERR_INDEXER,
+ GITERR_SSL,
+ GITERR_SUBMODULE,
+ GITERR_THREAD,
+ GITERR_STASH,
+ GITERR_CHECKOUT,
+ GITERR_FETCHHEAD,
+ GITERR_MERGE,
+ GITERR_SSH,
+ GITERR_FILTER,
+ GITERR_REVERT,
+ GITERR_CALLBACK,
+ GITERR_CHERRYPICK,
+ GITERR_DESCRIBE,
+ GITERR_REBASE,
+ GITERR_FILESYSTEM,
+ GITERR_PATCH,
+} git_error_t;
+
+/**
+ * Return the last `git_error` object that was generated for the
+ * current thread or NULL if no error has occurred.
+ *
+ * @return A git_error object.
+ */
+GIT_EXTERN(const git_error *) giterr_last(void);
+
+/**
+ * Clear the last library error that occurred for this thread.
+ */
+GIT_EXTERN(void) giterr_clear(void);
+
+/**
+ * Set the error message string for this thread.
+ *
+ * This function is public so that custom ODB backends and the like can
+ * relay an error message through libgit2. Most regular users of libgit2
+ * will never need to call this function -- actually, calling it in most
+ * circumstances (for example, calling from within a callback function)
+ * will just end up having the value overwritten by libgit2 internals.
+ *
+ * This error message is stored in thread-local storage and only applies
+ * to the particular thread that this libgit2 call is made from.
+ *
+ * @param error_class One of the `git_error_t` enum above describing the
+ * general subsystem that is responsible for the error.
+ * @param string The formatted error message to keep
+ */
+GIT_EXTERN(void) giterr_set_str(int error_class, const char *string);
+
+/**
+ * Set the error message to a special value for memory allocation failure.
+ *
+ * The normal `giterr_set_str()` function attempts to `strdup()` the string
+ * that is passed in. This is not a good idea when the error in question
+ * is a memory allocation failure. That circumstance has a special setter
+ * function that sets the error string to a known and statically allocated
+ * internal value.
+ */
+GIT_EXTERN(void) giterr_set_oom(void);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_filter_h__
+#define INCLUDE_git_filter_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "buffer.h"
+
+/**
+ * @file git2/filter.h
+ * @brief Git filter APIs
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Filters are applied in one of two directions: smudging - which is
+ * exporting a file from the Git object database to the working directory,
+ * and cleaning - which is importing a file from the working directory to
+ * the Git object database. These values control which direction of
+ * change is being applied.
+ */
+typedef enum {
+ GIT_FILTER_TO_WORKTREE = 0,
+ GIT_FILTER_SMUDGE = GIT_FILTER_TO_WORKTREE,
+ GIT_FILTER_TO_ODB = 1,
+ GIT_FILTER_CLEAN = GIT_FILTER_TO_ODB,
+} git_filter_mode_t;
+
+/**
+ * Filter option flags.
+ */
+typedef enum {
+ GIT_FILTER_DEFAULT = 0u,
+ GIT_FILTER_ALLOW_UNSAFE = (1u << 0),
+} git_filter_flag_t;
+
+/**
+ * A filter that can transform file data
+ *
+ * This represents a filter that can be used to transform or even replace
+ * file data. Libgit2 includes one built in filter and it is possible to
+ * write your own (see git2/sys/filter.h for information on that).
+ *
+ * The two builtin filters are:
+ *
+ * * "crlf" which uses the complex rules with the "text", "eol", and
+ * "crlf" file attributes to decide how to convert between LF and CRLF
+ * line endings
+ * * "ident" which replaces "$Id$" in a blob with "$Id: <blob OID>$" upon
+ * checkout and replaced "$Id: <anything>$" with "$Id$" on checkin.
+ */
+typedef struct git_filter git_filter;
+
+/**
+ * List of filters to be applied
+ *
+ * This represents a list of filters to be applied to a file / blob. You
+ * can build the list with one call, apply it with another, and dispose it
+ * with a third. In typical usage, there are not many occasions where a
+ * git_filter_list is needed directly since the library will generally
+ * handle conversions for you, but it can be convenient to be able to
+ * build and apply the list sometimes.
+ */
+typedef struct git_filter_list git_filter_list;
+
+/**
+ * Load the filter list for a given path.
+ *
+ * This will return 0 (success) but set the output git_filter_list to NULL
+ * if no filters are requested for the given file.
+ *
+ * @param filters Output newly created git_filter_list (or NULL)
+ * @param repo Repository object that contains `path`
+ * @param blob The blob to which the filter will be applied (if known)
+ * @param path Relative path of the file to be filtered
+ * @param mode Filtering direction (WT->ODB or ODB->WT)
+ * @param flags Combination of `git_filter_flag_t` flags
+ * @return 0 on success (which could still return NULL if no filters are
+ * needed for the requested file), <0 on error
+ */
+GIT_EXTERN(int) git_filter_list_load(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ uint32_t flags);
+
+/**
+ * Query the filter list to see if a given filter (by name) will run.
+ * The built-in filters "crlf" and "ident" can be queried, otherwise this
+ * is the name of the filter specified by the filter attribute.
+ *
+ * This will return 0 if the given filter is not in the list, or 1 if
+ * the filter will be applied.
+ *
+ * @param filters A loaded git_filter_list (or NULL)
+ * @param name The name of the filter to query
+ * @return 1 if the filter is in the list, 0 otherwise
+ */
+GIT_EXTERN(int) git_filter_list_contains(
+ git_filter_list *filters,
+ const char *name);
+
+/**
+ * Apply filter list to a data buffer.
+ *
+ * See `git2/buffer.h` for background on `git_buf` objects.
+ *
+ * If the `in` buffer holds data allocated by libgit2 (i.e. `in->asize` is
+ * not zero), then it will be overwritten when applying the filters. If
+ * not, then it will be left untouched.
+ *
+ * If there are no filters to apply (or `filters` is NULL), then the `out`
+ * buffer will reference the `in` buffer data (with `asize` set to zero)
+ * instead of allocating data. This keeps allocations to a minimum, but
+ * it means you have to be careful about freeing the `in` data since `out`
+ * may be pointing to it!
+ *
+ * @param out Buffer to store the result of the filtering
+ * @param filters A loaded git_filter_list (or NULL)
+ * @param in Buffer containing the data to filter
+ * @return 0 on success, an error code otherwise
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_data(
+ git_buf *out,
+ git_filter_list *filters,
+ git_buf *in);
+
+/**
+ * Apply a filter list to the contents of a file on disk
+ *
+ * @param out buffer into which to store the filtered file
+ * @param filters the list of filters to apply
+ * @param repo the repository in which to perform the filtering
+ * @param path the path of the file to filter, a relative path will be
+ * taken as relative to the workdir
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_file(
+ git_buf *out,
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path);
+
+/**
+ * Apply a filter list to the contents of a blob
+ *
+ * @param out buffer into which to store the filtered file
+ * @param filters the list of filters to apply
+ * @param blob the blob to filter
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_blob(
+ git_buf *out,
+ git_filter_list *filters,
+ git_blob *blob);
+
+/**
+ * Apply a filter list to an arbitrary buffer as a stream
+ *
+ * @param filters the list of filters to apply
+ * @param data the buffer to filter
+ * @param target the stream into which the data will be written
+ */
+GIT_EXTERN(int) git_filter_list_stream_data(
+ git_filter_list *filters,
+ git_buf *data,
+ git_writestream *target);
+
+/**
+ * Apply a filter list to a file as a stream
+ *
+ * @param filters the list of filters to apply
+ * @param repo the repository in which to perform the filtering
+ * @param path the path of the file to filter, a relative path will be
+ * taken as relative to the workdir
+ * @param target the stream into which the data will be written
+ */
+GIT_EXTERN(int) git_filter_list_stream_file(
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path,
+ git_writestream *target);
+
+/**
+ * Apply a filter list to a blob as a stream
+ *
+ * @param filters the list of filters to apply
+ * @param blob the blob to filter
+ * @param target the stream into which the data will be written
+ */
+GIT_EXTERN(int) git_filter_list_stream_blob(
+ git_filter_list *filters,
+ git_blob *blob,
+ git_writestream *target);
+
+/**
+ * Free a git_filter_list
+ *
+ * @param filters A git_filter_list created by `git_filter_list_load`
+ */
+GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters);
+
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_global_h__
+#define INCLUDE_git_global_h__
+
+#include "common.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Init the global state
+ *
+ * This function must the called before any other libgit2 function in
+ * order to set up global state and threading.
+ *
+ * This function may be called multiple times - it will return the number
+ * of times the initialization has been called (including this one) that have
+ * not subsequently been shutdown.
+ *
+ * @return the number of initializations of the library, or an error code.
+ */
+GIT_EXTERN(int) git_libgit2_init(void);
+
+/**
+ * Shutdown the global state
+ *
+ * Clean up the global state and threading context after calling it as
+ * many times as `git_libgit2_init()` was called - it will return the
+ * number of remainining initializations that have not been shutdown
+ * (after this one).
+ *
+ * @return the number of remaining initializations of the library, or an
+ * error code.
+ */
+GIT_EXTERN(int) git_libgit2_shutdown(void);
+
+/** @} */
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_graph_h__
+#define INCLUDE_git_graph_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+/**
+ * @file git2/graph.h
+ * @brief Git graph traversal routines
+ * @defgroup git_revwalk Git graph traversal routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Count the number of unique commits between two commit objects
+ *
+ * There is no need for branches containing the commits to have any
+ * upstream relationship, but it helps to think of one as a branch and
+ * the other as its upstream, the `ahead` and `behind` values will be
+ * what git would report for the branches.
+ *
+ * @param ahead number of unique from commits in `upstream`
+ * @param behind number of unique from commits in `local`
+ * @param repo the repository where the commits exist
+ * @param local the commit for local
+ * @param upstream the commit for upstream
+ */
+GIT_EXTERN(int) git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, const git_oid *local, const git_oid *upstream);
+
+
+/**
+ * Determine if a commit is the descendant of another commit.
+ *
+ * @param commit a previously loaded commit.
+ * @param ancestor a potential ancestor commit.
+ * @return 1 if the given commit is a descendant of the potential ancestor,
+ * 0 if not, error code otherwise.
+ */
+GIT_EXTERN(int) git_graph_descendant_of(
+ git_repository *repo,
+ const git_oid *commit,
+ const git_oid *ancestor);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_ignore_h__
+#define INCLUDE_git_ignore_h__
+
+#include "common.h"
+#include "types.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Add ignore rules for a repository.
+ *
+ * Excludesfile rules (i.e. .gitignore rules) are generally read from
+ * .gitignore files in the repository tree or from a shared system file
+ * only if a "core.excludesfile" config value is set. The library also
+ * keeps a set of per-repository internal ignores that can be configured
+ * in-memory and will not persist. This function allows you to add to
+ * that internal rules list.
+ *
+ * Example usage:
+ *
+ * error = git_ignore_add_rule(myrepo, "*.c\ndir/\nFile with space\n");
+ *
+ * This would add three rules to the ignores.
+ *
+ * @param repo The repository to add ignore rules to.
+ * @param rules Text of rules, a la the contents of a .gitignore file.
+ * It is okay to have multiple rules in the text; if so,
+ * each rule should be terminated with a newline.
+ * @return 0 on success
+ */
+GIT_EXTERN(int) git_ignore_add_rule(
+ git_repository *repo,
+ const char *rules);
+
+/**
+ * Clear ignore rules that were explicitly added.
+ *
+ * Resets to the default internal ignore rules. This will not turn off
+ * rules in .gitignore files that actually exist in the filesystem.
+ *
+ * The default internal ignores ignore ".", ".." and ".git" entries.
+ *
+ * @param repo The repository to remove ignore rules from.
+ * @return 0 on success
+ */
+GIT_EXTERN(int) git_ignore_clear_internal_rules(
+ git_repository *repo);
+
+/**
+ * Test if the ignore rules apply to a given path.
+ *
+ * This function checks the ignore rules to see if they would apply to the
+ * given file. This indicates if the file would be ignored regardless of
+ * whether the file is already in the index or committed to the repository.
+ *
+ * One way to think of this is if you were to do "git add ." on the
+ * directory containing the file, would it be added or not?
+ *
+ * @param ignored boolean returning 0 if the file is not ignored, 1 if it is
+ * @param repo a repository object
+ * @param path the file to check ignores for, relative to the repo's workdir.
+ * @return 0 if ignore rules could be processed for the file (regardless
+ * of whether it exists or not), or an error < 0 if they could not.
+ */
+GIT_EXTERN(int) git_ignore_path_is_ignored(
+ int *ignored,
+ git_repository *repo,
+ const char *path);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_index_h__
+#define INCLUDE_git_index_h__
+
+#include "common.h"
+#include "indexer.h"
+#include "types.h"
+#include "oid.h"
+#include "strarray.h"
+
+/**
+ * @file git2/index.h
+ * @brief Git index parsing and manipulation routines
+ * @defgroup git_index Git index parsing and manipulation routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Time structure used in a git index entry */
+typedef struct {
+ int32_t seconds;
+ /* nsec should not be stored as time_t compatible */
+ uint32_t nanoseconds;
+} git_index_time;
+
+/**
+ * In-memory representation of a file entry in the index.
+ *
+ * This is a public structure that represents a file entry in the index.
+ * The meaning of the fields corresponds to core Git's documentation (in
+ * "Documentation/technical/index-format.txt").
+ *
+ * The `flags` field consists of a number of bit fields which can be
+ * accessed via the first set of `GIT_IDXENTRY_...` bitmasks below. These
+ * flags are all read from and persisted to disk.
+ *
+ * The `flags_extended` field also has a number of bit fields which can be
+ * accessed via the later `GIT_IDXENTRY_...` bitmasks below. Some of
+ * these flags are read from and written to disk, but some are set aside
+ * for in-memory only reference.
+ *
+ * Note that the time and size fields are truncated to 32 bits. This
+ * is enough to detect changes, which is enough for the index to
+ * function as a cache, but it should not be taken as an authoritative
+ * source for that data.
+ */
+typedef struct git_index_entry {
+ git_index_time ctime;
+ git_index_time mtime;
+
+ uint32_t dev;
+ uint32_t ino;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t file_size;
+
+ git_oid id;
+
+ uint16_t flags;
+ uint16_t flags_extended;
+
+ const char *path;
+} git_index_entry;
+
+/**
+ * Bitmasks for on-disk fields of `git_index_entry`'s `flags`
+ *
+ * These bitmasks match the four fields in the `git_index_entry` `flags`
+ * value both in memory and on disk. You can use them to interpret the
+ * data in the `flags`.
+ */
+#define GIT_IDXENTRY_NAMEMASK (0x0fff)
+#define GIT_IDXENTRY_STAGEMASK (0x3000)
+#define GIT_IDXENTRY_STAGESHIFT 12
+
+/**
+ * Flags for index entries
+ */
+typedef enum {
+ GIT_IDXENTRY_EXTENDED = (0x4000),
+ GIT_IDXENTRY_VALID = (0x8000),
+} git_indxentry_flag_t;
+
+#define GIT_IDXENTRY_STAGE(E) \
+ (((E)->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT)
+
+#define GIT_IDXENTRY_STAGE_SET(E,S) do { \
+ (E)->flags = ((E)->flags & ~GIT_IDXENTRY_STAGEMASK) | \
+ (((S) & 0x03) << GIT_IDXENTRY_STAGESHIFT); } while (0)
+
+/**
+ * Bitmasks for on-disk fields of `git_index_entry`'s `flags_extended`
+ *
+ * In memory, the `flags_extended` fields are divided into two parts: the
+ * fields that are read from and written to disk, and other fields that
+ * in-memory only and used by libgit2. Only the flags in
+ * `GIT_IDXENTRY_EXTENDED_FLAGS` will get saved on-disk.
+ *
+ * Thee first three bitmasks match the three fields in the
+ * `git_index_entry` `flags_extended` value that belong on disk. You
+ * can use them to interpret the data in the `flags_extended`.
+ *
+ * The rest of the bitmasks match the other fields in the `git_index_entry`
+ * `flags_extended` value that are only used in-memory by libgit2.
+ * You can use them to interpret the data in the `flags_extended`.
+ *
+ */
+typedef enum {
+
+ GIT_IDXENTRY_INTENT_TO_ADD = (1 << 13),
+ GIT_IDXENTRY_SKIP_WORKTREE = (1 << 14),
+ /** Reserved for future extension */
+ GIT_IDXENTRY_EXTENDED2 = (1 << 15),
+
+ GIT_IDXENTRY_EXTENDED_FLAGS = (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE),
+ GIT_IDXENTRY_UPDATE = (1 << 0),
+ GIT_IDXENTRY_REMOVE = (1 << 1),
+ GIT_IDXENTRY_UPTODATE = (1 << 2),
+ GIT_IDXENTRY_ADDED = (1 << 3),
+
+ GIT_IDXENTRY_HASHED = (1 << 4),
+ GIT_IDXENTRY_UNHASHED = (1 << 5),
+ GIT_IDXENTRY_WT_REMOVE = (1 << 6), /**< remove in work directory */
+ GIT_IDXENTRY_CONFLICTED = (1 << 7),
+
+ GIT_IDXENTRY_UNPACKED = (1 << 8),
+ GIT_IDXENTRY_NEW_SKIP_WORKTREE = (1 << 9),
+} git_idxentry_extended_flag_t;
+
+/** Capabilities of system that affect index actions. */
+typedef enum {
+ GIT_INDEXCAP_IGNORE_CASE = 1,
+ GIT_INDEXCAP_NO_FILEMODE = 2,
+ GIT_INDEXCAP_NO_SYMLINKS = 4,
+ GIT_INDEXCAP_FROM_OWNER = -1,
+} git_indexcap_t;
+
+/** Callback for APIs that add/remove/update files matching pathspec */
+typedef int (*git_index_matched_path_cb)(
+ const char *path, const char *matched_pathspec, void *payload);
+
+/** Flags for APIs that add files matching pathspec */
+typedef enum {
+ GIT_INDEX_ADD_DEFAULT = 0,
+ GIT_INDEX_ADD_FORCE = (1u << 0),
+ GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = (1u << 1),
+ GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2),
+} git_index_add_option_t;
+
+typedef enum {
+ /**
+ * Match any index stage.
+ *
+ * Some index APIs take a stage to match; pass this value to match
+ * any entry matching the path regardless of stage.
+ */
+ GIT_INDEX_STAGE_ANY = -1,
+
+ /** A normal staged file in the index. */
+ GIT_INDEX_STAGE_NORMAL = 0,
+
+ /** The ancestor side of a conflict. */
+ GIT_INDEX_STAGE_ANCESTOR = 1,
+
+ /** The "ours" side of a conflict. */
+ GIT_INDEX_STAGE_OURS = 2,
+
+ /** The "theirs" side of a conflict. */
+ GIT_INDEX_STAGE_THEIRS = 3,
+} git_index_stage_t;
+
+/** @name Index File Functions
+ *
+ * These functions work on the index file itself.
+ */
+/**@{*/
+
+/**
+ * Create a new bare Git index object as a memory representation
+ * of the Git index file in 'index_path', without a repository
+ * to back it.
+ *
+ * Since there is no ODB or working directory behind this index,
+ * any Index methods which rely on these (e.g. index_add_bypath)
+ * will fail with the GIT_ERROR error code.
+ *
+ * If you need to access the index of an actual repository,
+ * use the `git_repository_index` wrapper.
+ *
+ * The index must be freed once it's no longer in use.
+ *
+ * @param out the pointer for the new index
+ * @param index_path the path to the index file in disk
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path);
+
+/**
+ * Create an in-memory index object.
+ *
+ * This index object cannot be read/written to the filesystem,
+ * but may be used to perform in-memory index operations.
+ *
+ * The index must be freed once it's no longer in use.
+ *
+ * @param out the pointer for the new index
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_new(git_index **out);
+
+/**
+ * Free an existing index object.
+ *
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_free(git_index *index);
+
+/**
+ * Get the repository this index relates to
+ *
+ * @param index The index
+ * @return A pointer to the repository
+ */
+GIT_EXTERN(git_repository *) git_index_owner(const git_index *index);
+
+/**
+ * Read index capabilities flags.
+ *
+ * @param index An existing index object
+ * @return A combination of GIT_INDEXCAP values
+ */
+GIT_EXTERN(int) git_index_caps(const git_index *index);
+
+/**
+ * Set index capabilities flags.
+ *
+ * If you pass `GIT_INDEXCAP_FROM_OWNER` for the caps, then the
+ * capabilities will be read from the config of the owner object,
+ * looking at `core.ignorecase`, `core.filemode`, `core.symlinks`.
+ *
+ * @param index An existing index object
+ * @param caps A combination of GIT_INDEXCAP values
+ * @return 0 on success, -1 on failure
+ */
+GIT_EXTERN(int) git_index_set_caps(git_index *index, int caps);
+
+/**
+ * Get index on-disk version.
+ *
+ * Valid return values are 2, 3, or 4. If 3 is returned, an index
+ * with version 2 may be written instead, if the extension data in
+ * version 3 is not necessary.
+ *
+ * @param index An existing index object
+ * @return the index version
+ */
+GIT_EXTERN(unsigned int) git_index_version(git_index *index);
+
+/**
+ * Set index on-disk version.
+ *
+ * Valid values are 2, 3, or 4. If 2 is given, git_index_write may
+ * write an index with version 3 instead, if necessary to accurately
+ * represent the index.
+ *
+ * @param index An existing index object
+ * @param version The new version number
+ * @return 0 on success, -1 on failure
+ */
+GIT_EXTERN(int) git_index_set_version(git_index *index, unsigned int version);
+
+/**
+ * Update the contents of an existing index object in memory by reading
+ * from the hard disk.
+ *
+ * If `force` is true, this performs a "hard" read that discards in-memory
+ * changes and always reloads the on-disk index data. If there is no
+ * on-disk version, the index will be cleared.
+ *
+ * If `force` is false, this does a "soft" read that reloads the index
+ * data from disk only if it has changed since the last time it was
+ * loaded. Purely in-memory index data will be untouched. Be aware: if
+ * there are changes on disk, unwritten in-memory changes are discarded.
+ *
+ * @param index an existing index object
+ * @param force if true, always reload, vs. only read if file has changed
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_read(git_index *index, int force);
+
+/**
+ * Write an existing index object from memory back to disk
+ * using an atomic file lock.
+ *
+ * @param index an existing index object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_write(git_index *index);
+
+/**
+ * Get the full path to the index file on disk.
+ *
+ * @param index an existing index object
+ * @return path to index file or NULL for in-memory index
+ */
+GIT_EXTERN(const char *) git_index_path(const git_index *index);
+
+/**
+ * Get the checksum of the index
+ *
+ * This checksum is the SHA-1 hash over the index file (except the
+ * last 20 bytes which are the checksum itself). In cases where the
+ * index does not exist on-disk, it will be zeroed out.
+ *
+ * @param index an existing index object
+ * @return a pointer to the checksum of the index
+ */
+GIT_EXTERN(const git_oid *) git_index_checksum(git_index *index);
+
+/**
+ * Read a tree into the index file with stats
+ *
+ * The current index contents will be replaced by the specified tree.
+ *
+ * @param index an existing index object
+ * @param tree tree to read
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_read_tree(git_index *index, const git_tree *tree);
+
+/**
+ * Write the index as a tree
+ *
+ * This method will scan the index and write a representation
+ * of its current state back to disk; it recursively creates
+ * tree objects for each of the subtrees stored in the index,
+ * but only returns the OID of the root tree. This is the OID
+ * that can be used e.g. to create a commit.
+ *
+ * The index instance cannot be bare, and needs to be associated
+ * to an existing repository.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param out Pointer where to store the OID of the written tree
+ * @param index Index to write
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree(git_oid *out, git_index *index);
+
+/**
+ * Write the index as a tree to the given repository
+ *
+ * This method will do the same as `git_index_write_tree`, but
+ * letting the user choose the repository where the tree will
+ * be written.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param out Pointer where to store OID of the the written tree
+ * @param index Index to write
+ * @param repo Repository where to write the tree
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree_to(git_oid *out, git_index *index, git_repository *repo);
+
+/**@}*/
+
+/** @name Raw Index Entry Functions
+ *
+ * These functions work on index entries, and allow for raw manipulation
+ * of the entries.
+ */
+/**@{*/
+
+/* Index entry manipulation */
+
+/**
+ * Get the count of entries currently in the index
+ *
+ * @param index an existing index object
+ * @return integer of count of current entries
+ */
+GIT_EXTERN(size_t) git_index_entrycount(const git_index *index);
+
+/**
+ * Clear the contents (all the entries) of an index object.
+ *
+ * This clears the index object in memory; changes must be explicitly
+ * written to disk for them to take effect persistently.
+ *
+ * @param index an existing index object
+ * @return 0 on success, error code < 0 on failure
+ */
+GIT_EXTERN(int) git_index_clear(git_index *index);
+
+/**
+ * Get a pointer to one of the entries in the index
+ *
+ * The entry is not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_entry *) git_index_get_byindex(
+ git_index *index, size_t n);
+
+/**
+ * Get a pointer to one of the entries in the index
+ *
+ * The entry is not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
+ * @return a pointer to the entry; NULL if it was not found
+ */
+GIT_EXTERN(const git_index_entry *) git_index_get_bypath(
+ git_index *index, const char *path, int stage);
+
+/**
+ * Remove an entry from the index
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage);
+
+/**
+ * Remove all entries from the index under a given directory
+ *
+ * @param index an existing index object
+ * @param dir container directory path
+ * @param stage stage to search
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_remove_directory(
+ git_index *index, const char *dir, int stage);
+
+/**
+ * Add or update an index entry from an in-memory struct
+ *
+ * If a previous index entry exists that has the same path and stage
+ * as the given 'source_entry', it will be replaced. Otherwise, the
+ * 'source_entry' will be added.
+ *
+ * A full copy (including the 'path' string) of the given
+ * 'source_entry' will be inserted on the index.
+ *
+ * @param index an existing index object
+ * @param source_entry new entry object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_entry);
+
+/**
+ * Return the stage number from a git index entry
+ *
+ * This entry is calculated from the entry's flag attribute like this:
+ *
+ * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT
+ *
+ * @param entry The entry
+ * @return the stage number
+ */
+GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
+
+/**
+ * Return whether the given index entry is a conflict (has a high stage
+ * entry). This is simply shorthand for `git_index_entry_stage > 0`.
+ *
+ * @param entry The entry
+ * @return 1 if the entry is a conflict entry, 0 otherwise
+ */
+GIT_EXTERN(int) git_index_entry_is_conflict(const git_index_entry *entry);
+
+/**@}*/
+
+/** @name Workdir Index Entry Functions
+ *
+ * These functions work on index entries specifically in the working
+ * directory (ie, stage 0).
+ */
+/**@{*/
+
+/**
+ * Add or update an index entry from a file on disk
+ *
+ * The file `path` must be relative to the repository's
+ * working folder and must be readable.
+ *
+ * This method will fail in bare index instances.
+ *
+ * This forces the file to be added to the index, not looking
+ * at gitignore rules. Those rules can be evaluated through
+ * the git_status APIs (in status.h) before calling this.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
+ * @param index an existing index object
+ * @param path filename to add
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path);
+
+/**
+ * Add or update an index entry from a buffer in memory
+ *
+ * This method will create a blob in the repository that owns the
+ * index and then add the index entry to the index. The `path` of the
+ * entry represents the position of the blob relative to the
+ * repository's root folder.
+ *
+ * If a previous index entry exists that has the same path as the
+ * given 'entry', it will be replaced. Otherwise, the 'entry' will be
+ * added. The `id` and the `file_size` of the 'entry' are updated with the
+ * real value of the blob.
+ *
+ * This forces the file to be added to the index, not looking
+ * at gitignore rules. Those rules can be evaluated through
+ * the git_status APIs (in status.h) before calling this.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
+ * @param index an existing index object
+ * @param entry filename to add
+ * @param buffer data to be written into the blob
+ * @param len length of the data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_add_frombuffer(
+ git_index *index,
+ const git_index_entry *entry,
+ const void *buffer, size_t len);
+
+/**
+ * Remove an index entry corresponding to a file on disk
+ *
+ * The file `path` must be relative to the repository's
+ * working folder. It may exist.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
+ * @param index an existing index object
+ * @param path filename to remove
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path);
+
+/**
+ * Add or update index entries matching files in the working directory.
+ *
+ * This method will fail in bare index instances.
+ *
+ * The `pathspec` is a list of file names or shell glob patterns that will
+ * matched against files in the repository's working directory. Each file
+ * that matches will be added to the index (either updating an existing
+ * entry or adding a new entry). You can disable glob expansion and force
+ * exact matching with the `GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH` flag.
+ *
+ * Files that are ignored will be skipped (unlike `git_index_add_bypath`).
+ * If a file is already tracked in the index, then it *will* be updated
+ * even if it is ignored. Pass the `GIT_INDEX_ADD_FORCE` flag to
+ * skip the checking of ignore rules.
+ *
+ * To emulate `git add -A` and generate an error if the pathspec contains
+ * the exact path of an ignored file (when not using FORCE), add the
+ * `GIT_INDEX_ADD_CHECK_PATHSPEC` flag. This checks that each entry
+ * in the `pathspec` that is an exact match to a filename on disk is
+ * either not ignored or already in the index. If this check fails, the
+ * function will return GIT_EINVALIDSPEC.
+ *
+ * To emulate `git add -A` with the "dry-run" option, just use a callback
+ * function that always returns a positive value. See below for details.
+ *
+ * If any files are currently the result of a merge conflict, those files
+ * will no longer be marked as conflicting. The data about the conflicts
+ * will be moved to the "resolve undo" (REUC) section.
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the working directory immediately *before* it is added to /
+ * updated in the index. Returning zero will add the item to the index,
+ * greater than zero will skip the item, and less than zero will abort the
+ * scan and return that value to the caller.
+ *
+ * @param index an existing index object
+ * @param pathspec array of path patterns
+ * @param flags combination of git_index_add_option_t flags
+ * @param callback notification callback for each added/updated path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
+ */
+GIT_EXTERN(int) git_index_add_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ unsigned int flags,
+ git_index_matched_path_cb callback,
+ void *payload);
+
+/**
+ * Remove all matching index entries.
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the index immediately *before* it is removed. Return 0 to
+ * remove the item, > 0 to skip the item, and < 0 to abort the scan.
+ *
+ * @param index An existing index object
+ * @param pathspec array of path patterns
+ * @param callback notification callback for each removed path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
+ */
+GIT_EXTERN(int) git_index_remove_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb callback,
+ void *payload);
+
+/**
+ * Update all index entries to match the working directory
+ *
+ * This method will fail in bare index instances.
+ *
+ * This scans the existing index entries and synchronizes them with the
+ * working directory, deleting them if the corresponding working directory
+ * file no longer exists otherwise updating the information (including
+ * adding the latest version of file to the ODB if needed).
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the index immediately *before* it is updated (either refreshed
+ * or removed depending on working directory state). Return 0 to proceed
+ * with updating the item, > 0 to skip the item, and < 0 to abort the scan.
+ *
+ * @param index An existing index object
+ * @param pathspec array of path patterns
+ * @param callback notification callback for each updated path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
+ */
+GIT_EXTERN(int) git_index_update_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb callback,
+ void *payload);
+
+/**
+ * Find the first position of any entries which point to given
+ * path in the Git index.
+ *
+ * @param at_pos the address to which the position of the index entry is written (optional)
+ * @param index an existing index object
+ * @param path path to search
+ * @return a zero-based position in the index if found; GIT_ENOTFOUND otherwise
+ */
+GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *path);
+
+/**
+ * Find the first position of any entries matching a prefix. To find the first position
+ * of a path inside a given folder, suffix the prefix with a '/'.
+ *
+ * @param at_pos the address to which the position of the index entry is written (optional)
+ * @param index an existing index object
+ * @param prefix the prefix to search for
+ * @return 0 with valid value in at_pos; an error code otherwise
+ */
+GIT_EXTERN(int) git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix);
+
+/**@}*/
+
+/** @name Conflict Index Entry Functions
+ *
+ * These functions work on conflict index entries specifically (ie, stages 1-3)
+ */
+/**@{*/
+
+/**
+ * Add or update index entries to represent a conflict. Any staged
+ * entries that exist at the given paths will be removed.
+ *
+ * The entries are the entries from the tree included in the merge. Any
+ * entry may be null to indicate that that file was not present in the
+ * trees during the merge. For example, ancestor_entry may be NULL to
+ * indicate that a file was added in both branches and must be resolved.
+ *
+ * @param index an existing index object
+ * @param ancestor_entry the entry data for the ancestor of the conflict
+ * @param our_entry the entry data for our side of the merge conflict
+ * @param their_entry the entry data for their side of the merge conflict
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_add(
+ git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry);
+
+/**
+ * Get the index entries that represent a conflict of a single file.
+ *
+ * The entries are not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
+ *
+ * @param ancestor_out Pointer to store the ancestor entry
+ * @param our_out Pointer to store the our entry
+ * @param their_out Pointer to store the their entry
+ * @param index an existing index object
+ * @param path path to search
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_get(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ const char *path);
+
+/**
+ * Removes the index entries that represent a conflict of a single file.
+ *
+ * @param index an existing index object
+ * @param path path to remove conflicts for
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path);
+
+/**
+ * Remove all conflicts in the index (entries with a stage greater than 0).
+ *
+ * @param index an existing index object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_cleanup(git_index *index);
+
+/**
+ * Determine if the index contains entries representing file conflicts.
+ *
+ * @return 1 if at least one conflict is found, 0 otherwise.
+ */
+GIT_EXTERN(int) git_index_has_conflicts(const git_index *index);
+
+/**
+ * Create an iterator for the conflicts in the index.
+ *
+ * The index must not be modified while iterating; the results are undefined.
+ *
+ * @param iterator_out The newly created conflict iterator
+ * @param index The index to scan
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_iterator_new(
+ git_index_conflict_iterator **iterator_out,
+ git_index *index);
+
+/**
+ * Returns the current conflict (ancestor, ours and theirs entry) and
+ * advance the iterator internally to the next value.
+ *
+ * @param ancestor_out Pointer to store the ancestor side of the conflict
+ * @param our_out Pointer to store our side of the conflict
+ * @param their_out Pointer to store their side of the conflict
+ * @return 0 (no error), GIT_ITEROVER (iteration is done) or an error code
+ * (negative value)
+ */
+GIT_EXTERN(int) git_index_conflict_next(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index_conflict_iterator *iterator);
+
+/**
+ * Frees a `git_index_conflict_iterator`.
+ *
+ * @param iterator pointer to the iterator
+ */
+GIT_EXTERN(void) git_index_conflict_iterator_free(
+ git_index_conflict_iterator *iterator);
+
+/**@}*/
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef _INCLUDE_git_indexer_h__
+#define _INCLUDE_git_indexer_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+GIT_BEGIN_DECL
+
+typedef struct git_indexer git_indexer;
+
+/**
+ * Create a new indexer instance
+ *
+ * @param out where to store the indexer instance
+ * @param path to the directory where the packfile should be stored
+ * @param mode permissions to use creating packfile or 0 for defaults
+ * @param odb object database from which to read base objects when
+ * fixing thin packs. Pass NULL if no thin pack is expected (an error
+ * will be returned if there are bases missing)
+ * @param progress_cb function to call with progress information
+ * @param progress_cb_payload payload for the progress callback
+ */
+GIT_EXTERN(int) git_indexer_new(
+ git_indexer **out,
+ const char *path,
+ unsigned int mode,
+ git_odb *odb,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload);
+
+/**
+ * Add data to the indexer
+ *
+ * @param idx the indexer
+ * @param data the data to add
+ * @param size the size of the data in bytes
+ * @param stats stat storage
+ */
+GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats);
+
+/**
+ * Finalize the pack and index
+ *
+ * Resolve any pending deltas and write out the index file
+ *
+ * @param idx the indexer
+ */
+GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_transfer_progress *stats);
+
+/**
+ * Get the packfile's hash
+ *
+ * A packfile's name is derived from the sorted hashing of all object
+ * names. This is only correct after the index has been finalized.
+ *
+ * @param idx the indexer instance
+ */
+GIT_EXTERN(const git_oid *) git_indexer_hash(const git_indexer *idx);
+
+/**
+ * Free the indexer and its resources
+ *
+ * @param idx the indexer to free
+ */
+GIT_EXTERN(void) git_indexer_free(git_indexer *idx);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+// ISO C9x compliant inttypes.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124
+//
+// Copyright (c) 2006 Alexander Chemeris
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. The name of the author may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_INTTYPES_H_ // [
+#define _MSC_INTTYPES_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#if _MSC_VER >= 1600
+#include <stdint.h>
+#else
+#include "stdint.h"
+#endif
+
+// 7.8 Format conversion of integer types
+
+typedef struct {
+ intmax_t quot;
+ intmax_t rem;
+} imaxdiv_t;
+
+// 7.8.1 Macros for format specifiers
+
+#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198
+
+// The fprintf macros for signed integers are:
+#define PRId8 "d"
+#define PRIi8 "i"
+#define PRIdLEAST8 "d"
+#define PRIiLEAST8 "i"
+#define PRIdFAST8 "d"
+#define PRIiFAST8 "i"
+
+#define PRId16 "hd"
+#define PRIi16 "hi"
+#define PRIdLEAST16 "hd"
+#define PRIiLEAST16 "hi"
+#define PRIdFAST16 "hd"
+#define PRIiFAST16 "hi"
+
+#define PRId32 "I32d"
+#define PRIi32 "I32i"
+#define PRIdLEAST32 "I32d"
+#define PRIiLEAST32 "I32i"
+#define PRIdFAST32 "I32d"
+#define PRIiFAST32 "I32i"
+
+#define PRId64 "I64d"
+#define PRIi64 "I64i"
+#define PRIdLEAST64 "I64d"
+#define PRIiLEAST64 "I64i"
+#define PRIdFAST64 "I64d"
+#define PRIiFAST64 "I64i"
+
+#define PRIdMAX "I64d"
+#define PRIiMAX "I64i"
+
+#define PRIdPTR "Id"
+#define PRIiPTR "Ii"
+
+// The fprintf macros for unsigned integers are:
+#define PRIo8 "o"
+#define PRIu8 "u"
+#define PRIx8 "x"
+#define PRIX8 "X"
+#define PRIoLEAST8 "o"
+#define PRIuLEAST8 "u"
+#define PRIxLEAST8 "x"
+#define PRIXLEAST8 "X"
+#define PRIoFAST8 "o"
+#define PRIuFAST8 "u"
+#define PRIxFAST8 "x"
+#define PRIXFAST8 "X"
+
+#define PRIo16 "ho"
+#define PRIu16 "hu"
+#define PRIx16 "hx"
+#define PRIX16 "hX"
+#define PRIoLEAST16 "ho"
+#define PRIuLEAST16 "hu"
+#define PRIxLEAST16 "hx"
+#define PRIXLEAST16 "hX"
+#define PRIoFAST16 "ho"
+#define PRIuFAST16 "hu"
+#define PRIxFAST16 "hx"
+#define PRIXFAST16 "hX"
+
+#define PRIo32 "I32o"
+#define PRIu32 "I32u"
+#define PRIx32 "I32x"
+#define PRIX32 "I32X"
+#define PRIoLEAST32 "I32o"
+#define PRIuLEAST32 "I32u"
+#define PRIxLEAST32 "I32x"
+#define PRIXLEAST32 "I32X"
+#define PRIoFAST32 "I32o"
+#define PRIuFAST32 "I32u"
+#define PRIxFAST32 "I32x"
+#define PRIXFAST32 "I32X"
+
+#define PRIo64 "I64o"
+#define PRIu64 "I64u"
+#define PRIx64 "I64x"
+#define PRIX64 "I64X"
+#define PRIoLEAST64 "I64o"
+#define PRIuLEAST64 "I64u"
+#define PRIxLEAST64 "I64x"
+#define PRIXLEAST64 "I64X"
+#define PRIoFAST64 "I64o"
+#define PRIuFAST64 "I64u"
+#define PRIxFAST64 "I64x"
+#define PRIXFAST64 "I64X"
+
+#define PRIoMAX "I64o"
+#define PRIuMAX "I64u"
+#define PRIxMAX "I64x"
+#define PRIXMAX "I64X"
+
+#define PRIoPTR "Io"
+#define PRIuPTR "Iu"
+#define PRIxPTR "Ix"
+#define PRIXPTR "IX"
+
+// The fscanf macros for signed integers are:
+#define SCNd8 "d"
+#define SCNi8 "i"
+#define SCNdLEAST8 "d"
+#define SCNiLEAST8 "i"
+#define SCNdFAST8 "d"
+#define SCNiFAST8 "i"
+
+#define SCNd16 "hd"
+#define SCNi16 "hi"
+#define SCNdLEAST16 "hd"
+#define SCNiLEAST16 "hi"
+#define SCNdFAST16 "hd"
+#define SCNiFAST16 "hi"
+
+#define SCNd32 "ld"
+#define SCNi32 "li"
+#define SCNdLEAST32 "ld"
+#define SCNiLEAST32 "li"
+#define SCNdFAST32 "ld"
+#define SCNiFAST32 "li"
+
+#define SCNd64 "I64d"
+#define SCNi64 "I64i"
+#define SCNdLEAST64 "I64d"
+#define SCNiLEAST64 "I64i"
+#define SCNdFAST64 "I64d"
+#define SCNiFAST64 "I64i"
+
+#define SCNdMAX "I64d"
+#define SCNiMAX "I64i"
+
+#ifdef _WIN64 // [
+# define SCNdPTR "I64d"
+# define SCNiPTR "I64i"
+#else // _WIN64 ][
+# define SCNdPTR "ld"
+# define SCNiPTR "li"
+#endif // _WIN64 ]
+
+// The fscanf macros for unsigned integers are:
+#define SCNo8 "o"
+#define SCNu8 "u"
+#define SCNx8 "x"
+#define SCNX8 "X"
+#define SCNoLEAST8 "o"
+#define SCNuLEAST8 "u"
+#define SCNxLEAST8 "x"
+#define SCNXLEAST8 "X"
+#define SCNoFAST8 "o"
+#define SCNuFAST8 "u"
+#define SCNxFAST8 "x"
+#define SCNXFAST8 "X"
+
+#define SCNo16 "ho"
+#define SCNu16 "hu"
+#define SCNx16 "hx"
+#define SCNX16 "hX"
+#define SCNoLEAST16 "ho"
+#define SCNuLEAST16 "hu"
+#define SCNxLEAST16 "hx"
+#define SCNXLEAST16 "hX"
+#define SCNoFAST16 "ho"
+#define SCNuFAST16 "hu"
+#define SCNxFAST16 "hx"
+#define SCNXFAST16 "hX"
+
+#define SCNo32 "lo"
+#define SCNu32 "lu"
+#define SCNx32 "lx"
+#define SCNX32 "lX"
+#define SCNoLEAST32 "lo"
+#define SCNuLEAST32 "lu"
+#define SCNxLEAST32 "lx"
+#define SCNXLEAST32 "lX"
+#define SCNoFAST32 "lo"
+#define SCNuFAST32 "lu"
+#define SCNxFAST32 "lx"
+#define SCNXFAST32 "lX"
+
+#define SCNo64 "I64o"
+#define SCNu64 "I64u"
+#define SCNx64 "I64x"
+#define SCNX64 "I64X"
+#define SCNoLEAST64 "I64o"
+#define SCNuLEAST64 "I64u"
+#define SCNxLEAST64 "I64x"
+#define SCNXLEAST64 "I64X"
+#define SCNoFAST64 "I64o"
+#define SCNuFAST64 "I64u"
+#define SCNxFAST64 "I64x"
+#define SCNXFAST64 "I64X"
+
+#define SCNoMAX "I64o"
+#define SCNuMAX "I64u"
+#define SCNxMAX "I64x"
+#define SCNXMAX "I64X"
+
+#ifdef _WIN64 // [
+# define SCNoPTR "I64o"
+# define SCNuPTR "I64u"
+# define SCNxPTR "I64x"
+# define SCNXPTR "I64X"
+#else // _WIN64 ][
+# define SCNoPTR "lo"
+# define SCNuPTR "lu"
+# define SCNxPTR "lx"
+# define SCNXPTR "lX"
+#endif // _WIN64 ]
+
+#endif // __STDC_FORMAT_MACROS ]
+
+// 7.8.2 Functions for greatest-width integer types
+
+// 7.8.2.1 The imaxabs function
+#define imaxabs _abs64
+
+// 7.8.2.2 The imaxdiv function
+
+// This is modified version of div() function from Microsoft's div.c found
+// in %MSVC.NET%\crt\src\div.c
+#ifdef STATIC_IMAXDIV // [
+static
+#else // STATIC_IMAXDIV ][
+_inline
+#endif // STATIC_IMAXDIV ]
+imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom)
+{
+ imaxdiv_t result;
+
+ result.quot = numer / denom;
+ result.rem = numer % denom;
+
+ if (numer < 0 && result.rem > 0) {
+ // did division wrong; must fix up
+ ++result.quot;
+ result.rem -= denom;
+ }
+
+ return result;
+}
+
+// 7.8.2.3 The strtoimax and strtoumax functions
+#define strtoimax _strtoi64
+#define strtoumax _strtoui64
+
+// 7.8.2.4 The wcstoimax and wcstoumax functions
+#define wcstoimax _wcstoi64
+#define wcstoumax _wcstoui64
+
+
+#endif // _MSC_INTTYPES_H_ ]
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_merge_h__
+#define INCLUDE_git_merge_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "oidarray.h"
+#include "checkout.h"
+#include "index.h"
+#include "annotated_commit.h"
+
+/**
+ * @file git2/merge.h
+ * @brief Git merge routines
+ * @defgroup git_merge Git merge routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * The file inputs to `git_merge_file`. Callers should populate the
+ * `git_merge_file_input` structure with descriptions of the files in
+ * each side of the conflict for use in producing the merge file.
+ */
+typedef struct {
+ unsigned int version;
+
+ /** Pointer to the contents of the file. */
+ const char *ptr;
+
+ /** Size of the contents pointed to in `ptr`. */
+ size_t size;
+
+ /** File name of the conflicted file, or `NULL` to not merge the path. */
+ const char *path;
+
+ /** File mode of the conflicted file, or `0` to not merge the mode. */
+ unsigned int mode;
+} git_merge_file_input;
+
+#define GIT_MERGE_FILE_INPUT_VERSION 1
+#define GIT_MERGE_FILE_INPUT_INIT {GIT_MERGE_FILE_INPUT_VERSION}
+
+/**
+ * Initializes a `git_merge_file_input` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_FILE_INPUT_INIT.
+ *
+ * @param opts the `git_merge_file_input` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_FILE_INPUT_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_file_init_input(
+ git_merge_file_input *opts,
+ unsigned int version);
+
+/**
+ * Flags for `git_merge` options. A combination of these flags can be
+ * passed in via the `flags` value in the `git_merge_options`.
+ */
+typedef enum {
+ /**
+ * Detect renames that occur between the common ancestor and the "ours"
+ * side or the common ancestor and the "theirs" side. This will enable
+ * the ability to merge between a modified and renamed file.
+ */
+ GIT_MERGE_FIND_RENAMES = (1 << 0),
+
+ /**
+ * If a conflict occurs, exit immediately instead of attempting to
+ * continue resolving conflicts. The merge operation will fail with
+ * GIT_EMERGECONFLICT and no index will be returned.
+ */
+ GIT_MERGE_FAIL_ON_CONFLICT = (1 << 1),
+
+ /**
+ * Do not write the REUC extension on the generated index
+ */
+ GIT_MERGE_SKIP_REUC = (1 << 2),
+
+ /**
+ * If the commits being merged have multiple merge bases, do not build
+ * a recursive merge base (by merging the multiple merge bases),
+ * instead simply use the first base. This flag provides a similar
+ * merge base to `git-merge-resolve`.
+ */
+ GIT_MERGE_NO_RECURSIVE = (1 << 3),
+} git_merge_flag_t;
+
+/**
+ * Merge file favor options for `git_merge_options` instruct the file-level
+ * merging functionality how to deal with conflicting regions of the files.
+ */
+typedef enum {
+ /**
+ * When a region of a file is changed in both branches, a conflict
+ * will be recorded in the index so that `git_checkout` can produce
+ * a merge file with conflict markers in the working directory.
+ * This is the default.
+ */
+ GIT_MERGE_FILE_FAVOR_NORMAL = 0,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain the "ours" side of any conflicting
+ * region. The index will not record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_OURS = 1,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain the "theirs" side of any conflicting
+ * region. The index will not record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_THEIRS = 2,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain each unique line from each side,
+ * which has the result of combining both files. The index will not
+ * record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_UNION = 3,
+} git_merge_file_favor_t;
+
+/**
+ * File merging flags
+ */
+typedef enum {
+ /** Defaults */
+ GIT_MERGE_FILE_DEFAULT = 0,
+
+ /** Create standard conflicted merge files */
+ GIT_MERGE_FILE_STYLE_MERGE = (1 << 0),
+
+ /** Create diff3-style files */
+ GIT_MERGE_FILE_STYLE_DIFF3 = (1 << 1),
+
+ /** Condense non-alphanumeric regions for simplified diff file */
+ GIT_MERGE_FILE_SIMPLIFY_ALNUM = (1 << 2),
+
+ /** Ignore all whitespace */
+ GIT_MERGE_FILE_IGNORE_WHITESPACE = (1 << 3),
+
+ /** Ignore changes in amount of whitespace */
+ GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE = (1 << 4),
+
+ /** Ignore whitespace at end of line */
+ GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = (1 << 5),
+
+ /** Use the "patience diff" algorithm */
+ GIT_MERGE_FILE_DIFF_PATIENCE = (1 << 6),
+
+ /** Take extra time to find minimal diff */
+ GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7),
+} git_merge_file_flag_t;
+
+/**
+ * Options for merging a file
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Label for the ancestor file side of the conflict which will be prepended
+ * to labels in diff3-format merge files.
+ */
+ const char *ancestor_label;
+
+ /**
+ * Label for our file side of the conflict which will be prepended
+ * to labels in merge files.
+ */
+ const char *our_label;
+
+ /**
+ * Label for their file side of the conflict which will be prepended
+ * to labels in merge files.
+ */
+ const char *their_label;
+
+ /** The file to favor in region conflicts. */
+ git_merge_file_favor_t favor;
+
+ /** see `git_merge_file_flag_t` above */
+ git_merge_file_flag_t flags;
+} git_merge_file_options;
+
+#define GIT_MERGE_FILE_OPTIONS_VERSION 1
+#define GIT_MERGE_FILE_OPTIONS_INIT {GIT_MERGE_FILE_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_merge_file_options` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_FILE_OPTIONS_INIT.
+ *
+ * @param opts the `git_merge_file_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_FILE_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_file_init_options(
+ git_merge_file_options *opts,
+ unsigned int version);
+
+/**
+ * Information about file-level merging
+ */
+typedef struct {
+ /**
+ * True if the output was automerged, false if the output contains
+ * conflict markers.
+ */
+ unsigned int automergeable;
+
+ /**
+ * The path that the resultant merge file should use, or NULL if a
+ * filename conflict would occur.
+ */
+ const char *path;
+
+ /** The mode that the resultant merge file should use. */
+ unsigned int mode;
+
+ /** The contents of the merge. */
+ const char *ptr;
+
+ /** The length of the merge contents. */
+ size_t len;
+} git_merge_file_result;
+
+/**
+ * Merging options
+ */
+typedef struct {
+ unsigned int version;
+
+ /** See `git_merge_flag_t` above */
+ git_merge_flag_t flags;
+
+ /**
+ * Similarity to consider a file renamed (default 50). If
+ * `GIT_MERGE_FIND_RENAMES` is enabled, added files will be compared
+ * with deleted files to determine their similarity. Files that are
+ * more similar than the rename threshold (percentage-wise) will be
+ * treated as a rename.
+ */
+ unsigned int rename_threshold;
+
+ /**
+ * Maximum similarity sources to examine for renames (default 200).
+ * If the number of rename candidates (add / delete pairs) is greater
+ * than this value, inexact rename detection is aborted.
+ *
+ * This setting overrides the `merge.renameLimit` configuration value.
+ */
+ unsigned int target_limit;
+
+ /** Pluggable similarity metric; pass NULL to use internal metric */
+ git_diff_similarity_metric *metric;
+
+ /**
+ * Maximum number of times to merge common ancestors to build a
+ * virtual merge base when faced with criss-cross merges. When this
+ * limit is reached, the next ancestor will simply be used instead of
+ * attempting to merge it. The default is unlimited.
+ */
+ unsigned int recursion_limit;
+
+ /**
+ * Default merge driver to be used when both sides of a merge have
+ * changed. The default is the `text` driver.
+ */
+ const char *default_driver;
+
+ /**
+ * Flags for handling conflicting content, to be used with the standard
+ * (`text`) merge driver.
+ */
+ git_merge_file_favor_t file_favor;
+
+ /** see `git_merge_file_flag_t` above */
+ git_merge_file_flag_t file_flags;
+} git_merge_options;
+
+#define GIT_MERGE_OPTIONS_VERSION 1
+#define GIT_MERGE_OPTIONS_INIT {GIT_MERGE_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_merge_options` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_OPTIONS_INIT.
+ *
+ * @param opts the `git_merge_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_init_options(
+ git_merge_options *opts,
+ unsigned int version);
+
+/**
+ * The results of `git_merge_analysis` indicate the merge opportunities.
+ */
+typedef enum {
+ /** No merge is possible. (Unused.) */
+ GIT_MERGE_ANALYSIS_NONE = 0,
+
+ /**
+ * A "normal" merge; both HEAD and the given merge input have diverged
+ * from their common ancestor. The divergent commits must be merged.
+ */
+ GIT_MERGE_ANALYSIS_NORMAL = (1 << 0),
+
+ /**
+ * All given merge inputs are reachable from HEAD, meaning the
+ * repository is up-to-date and no merge needs to be performed.
+ */
+ GIT_MERGE_ANALYSIS_UP_TO_DATE = (1 << 1),
+
+ /**
+ * The given merge input is a fast-forward from HEAD and no merge
+ * needs to be performed. Instead, the client can check out the
+ * given merge input.
+ */
+ GIT_MERGE_ANALYSIS_FASTFORWARD = (1 << 2),
+
+ /**
+ * The HEAD of the current repository is "unborn" and does not point to
+ * a valid commit. No merge can be performed, but the caller may wish
+ * to simply set HEAD to the target commit(s).
+ */
+ GIT_MERGE_ANALYSIS_UNBORN = (1 << 3),
+} git_merge_analysis_t;
+
+/**
+ * The user's stated preference for merges.
+ */
+typedef enum {
+ /**
+ * No configuration was found that suggests a preferred behavior for
+ * merge.
+ */
+ GIT_MERGE_PREFERENCE_NONE = 0,
+
+ /**
+ * There is a `merge.ff=false` configuration setting, suggesting that
+ * the user does not want to allow a fast-forward merge.
+ */
+ GIT_MERGE_PREFERENCE_NO_FASTFORWARD = (1 << 0),
+
+ /**
+ * There is a `merge.ff=only` configuration setting, suggesting that
+ * the user only wants fast-forward merges.
+ */
+ GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = (1 << 1),
+} git_merge_preference_t;
+
+/**
+ * Analyzes the given branch(es) and determines the opportunities for
+ * merging them into the HEAD of the repository.
+ *
+ * @param analysis_out analysis enumeration that the result is written into
+ * @param repo the repository to merge
+ * @param their_heads the heads to merge into
+ * @param their_heads_len the number of heads to merge
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_analysis(
+ git_merge_analysis_t *analysis_out,
+ git_merge_preference_t *preference_out,
+ git_repository *repo,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len);
+
+/**
+ * Find a merge base between two commits
+ *
+ * @param out the OID of a merge base between 'one' and 'two'
+ * @param repo the repository where the commits exist
+ * @param one one of the commits
+ * @param two the other commit
+ * @return 0 on success, GIT_ENOTFOUND if not found or error code
+ */
+GIT_EXTERN(int) git_merge_base(
+ git_oid *out,
+ git_repository *repo,
+ const git_oid *one,
+ const git_oid *two);
+
+/**
+ * Find merge bases between two commits
+ *
+ * @param out array in which to store the resulting ids
+ * @param repo the repository where the commits exist
+ * @param one one of the commits
+ * @param two the other commit
+ * @return 0 on success, GIT_ENOTFOUND if not found or error code
+ */
+GIT_EXTERN(int) git_merge_bases(
+ git_oidarray *out,
+ git_repository *repo,
+ const git_oid *one,
+ const git_oid *two);
+
+/**
+ * Find a merge base given a list of commits
+ *
+ * @param out the OID of a merge base considering all the commits
+ * @param repo the repository where the commits exist
+ * @param length The number of commits in the provided `input_array`
+ * @param input_array oids of the commits
+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_base_many(
+ git_oid *out,
+ git_repository *repo,
+ size_t length,
+ const git_oid input_array[]);
+
+/**
+ * Find all merge bases given a list of commits
+ *
+ * @param out array in which to store the resulting ids
+ * @param repo the repository where the commits exist
+ * @param length The number of commits in the provided `input_array`
+ * @param input_array oids of the commits
+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_bases_many(
+ git_oidarray *out,
+ git_repository *repo,
+ size_t length,
+ const git_oid input_array[]);
+
+/**
+ * Find a merge base in preparation for an octopus merge
+ *
+ * @param out the OID of a merge base considering all the commits
+ * @param repo the repository where the commits exist
+ * @param length The number of commits in the provided `input_array`
+ * @param input_array oids of the commits
+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_base_octopus(
+ git_oid *out,
+ git_repository *repo,
+ size_t length,
+ const git_oid input_array[]);
+
+/**
+ * Merge two files as they exist in the in-memory data structures, using
+ * the given common ancestor as the baseline, producing a
+ * `git_merge_file_result` that reflects the merge result. The
+ * `git_merge_file_result` must be freed with `git_merge_file_result_free`.
+ *
+ * Note that this function does not reference a repository and any
+ * configuration must be passed as `git_merge_file_options`.
+ *
+ * @param out The git_merge_file_result to be filled in
+ * @param ancestor The contents of the ancestor file
+ * @param ours The contents of the file in "our" side
+ * @param theirs The contents of the file in "their" side
+ * @param opts The merge file options or `NULL` for defaults
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_file(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *opts);
+
+/**
+ * Merge two files as they exist in the index, using the given common
+ * ancestor as the baseline, producing a `git_merge_file_result` that
+ * reflects the merge result. The `git_merge_file_result` must be freed with
+ * `git_merge_file_result_free`.
+ *
+ * @param out The git_merge_file_result to be filled in
+ * @param repo The repository
+ * @param ancestor The index entry for the ancestor file (stage level 1)
+ * @param ours The index entry for our file (stage level 2)
+ * @param theirs The index entry for their file (stage level 3)
+ * @param opts The merge file options or NULL
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_file_from_index(
+ git_merge_file_result *out,
+ git_repository *repo,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ const git_merge_file_options *opts);
+
+/**
+ * Frees a `git_merge_file_result`.
+ *
+ * @param result The result to free or `NULL`
+ */
+GIT_EXTERN(void) git_merge_file_result_free(git_merge_file_result *result);
+
+/**
+ * Merge two trees, producing a `git_index` that reflects the result of
+ * the merge. The index may be written as-is to the working directory
+ * or checked out. If the index is to be converted to a tree, the caller
+ * should resolve any conflicts that arose as part of the merge.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo repository that contains the given trees
+ * @param ancestor_tree the common ancestor between the trees (or null if none)
+ * @param our_tree the tree that reflects the destination tree
+ * @param their_tree the tree to merge in to `our_tree`
+ * @param opts the merge tree options (or null for defaults)
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_trees(
+ git_index **out,
+ git_repository *repo,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree,
+ const git_merge_options *opts);
+
+/**
+ * Merge two commits, producing a `git_index` that reflects the result of
+ * the merge. The index may be written as-is to the working directory
+ * or checked out. If the index is to be converted to a tree, the caller
+ * should resolve any conflicts that arose as part of the merge.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo repository that contains the given trees
+ * @param our_commit the commit that reflects the destination tree
+ * @param their_commit the commit to merge in to `our_commit`
+ * @param opts the merge tree options (or null for defaults)
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_commits(
+ git_index **out,
+ git_repository *repo,
+ const git_commit *our_commit,
+ const git_commit *their_commit,
+ const git_merge_options *opts);
+
+/**
+ * Merges the given commit(s) into HEAD, writing the results into the working
+ * directory. Any changes are staged for commit and any conflicts are written
+ * to the index. Callers should inspect the repository's index after this
+ * completes, resolve any conflicts and prepare a commit.
+ *
+ * For compatibility with git, the repository is put into a merging
+ * state. Once the commit is done (or if the uses wishes to abort),
+ * you should clear this state by calling
+ * `git_repository_state_cleanup()`.
+ *
+ * @param repo the repository to merge
+ * @param their_heads the heads to merge into
+ * @param their_heads_len the number of heads to merge
+ * @param merge_opts merge options
+ * @param checkout_opts checkout options
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge(
+ git_repository *repo,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len,
+ const git_merge_options *merge_opts,
+ const git_checkout_options *checkout_opts);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_message_h__
+#define INCLUDE_git_message_h__
+
+#include "common.h"
+#include "buffer.h"
+
+/**
+ * @file git2/message.h
+ * @brief Git message management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Clean up message from excess whitespace and make sure that the last line
+ * ends with a '\n'.
+ *
+ * Optionally, can remove lines starting with a "#".
+ *
+ * @param out The user-allocated git_buf which will be filled with the
+ * cleaned up message.
+ *
+ * @param message The message to be prettified.
+ *
+ * @param strip_comments Non-zero to remove comment lines, 0 to leave them in.
+ *
+ * @param comment_char Comment character. Lines starting with this character
+ * are considered to be comments and removed if `strip_comments` is non-zero.
+ *
+ * @return 0 or an error code.
+ */
+GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments, char comment_char);
+
+/** @} */
+GIT_END_DECL
+
+#endif /* INCLUDE_git_message_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_net_h__
+#define INCLUDE_git_net_h__
+
+#include "common.h"
+#include "oid.h"
+#include "types.h"
+
+/**
+ * @file git2/net.h
+ * @brief Git networking declarations
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+#define GIT_DEFAULT_PORT "9418"
+
+/**
+ * Direction of the connection.
+ *
+ * We need this because we need to know whether we should call
+ * git-upload-pack or git-receive-pack on the remote end when get_refs
+ * gets called.
+ */
+typedef enum {
+ GIT_DIRECTION_FETCH = 0,
+ GIT_DIRECTION_PUSH = 1
+} git_direction;
+
+/**
+ * Description of a reference advertised by a remote server, given out
+ * on `ls` calls.
+ */
+struct git_remote_head {
+ int local; /* available locally */
+ git_oid oid;
+ git_oid loid;
+ char *name;
+ /**
+ * If the server send a symref mapping for this ref, this will
+ * point to the target.
+ */
+ char *symref_target;
+};
+
+/**
+ * Callback for listing the remote heads
+ */
+typedef int (*git_headlist_cb)(git_remote_head *rhead, void *payload);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_note_h__
+#define INCLUDE_git_note_h__
+
+#include "oid.h"
+
+/**
+ * @file git2/notes.h
+ * @brief Git notes management routines
+ * @defgroup git_note Git notes management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Callback for git_note_foreach.
+ *
+ * Receives:
+ * - blob_id: Oid of the blob containing the message
+ * - annotated_object_id: Oid of the git object being annotated
+ * - payload: Payload data passed to `git_note_foreach`
+ */
+typedef int (*git_note_foreach_cb)(
+ const git_oid *blob_id, const git_oid *annotated_object_id, void *payload);
+
+/**
+ * note iterator
+ */
+typedef struct git_iterator git_note_iterator;
+
+/**
+ * Creates a new iterator for notes
+ *
+ * The iterator must be freed manually by the user.
+ *
+ * @param out pointer to the iterator
+ * @param repo repository where to look up the note
+ * @param notes_ref canonical name of the reference to use (optional); defaults to
+ * "refs/notes/commits"
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_iterator_new(
+ git_note_iterator **out,
+ git_repository *repo,
+ const char *notes_ref);
+
+/**
+ * Frees an git_note_iterator
+ *
+ * @param it pointer to the iterator
+ */
+GIT_EXTERN(void) git_note_iterator_free(git_note_iterator *it);
+
+/**
+ * Return the current item (note_id and annotated_id) and advance the iterator
+ * internally to the next value
+ *
+ * @param note_id id of blob containing the message
+ * @param annotated_id id of the git object being annotated
+ * @param it pointer to the iterator
+ *
+ * @return 0 (no error), GIT_ITEROVER (iteration is done) or an error code
+ * (negative value)
+ */
+GIT_EXTERN(int) git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it);
+
+
+/**
+ * Read the note for an object
+ *
+ * The note must be freed manually by the user.
+ *
+ * @param out pointer to the read note; NULL in case of error
+ * @param repo repository where to look up the note
+ * @param notes_ref canonical name of the reference to use (optional); defaults to
+ * "refs/notes/commits"
+ * @param oid OID of the git object to read the note from
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_read(
+ git_note **out,
+ git_repository *repo,
+ const char *notes_ref,
+ const git_oid *oid);
+
+/**
+ * Get the note author
+ *
+ * @param note the note
+ * @return the author
+ */
+GIT_EXTERN(const git_signature *) git_note_author(const git_note *note);
+
+/**
+ * Get the note committer
+ *
+ * @param note the note
+ * @return the committer
+ */
+GIT_EXTERN(const git_signature *) git_note_committer(const git_note *note);
+
+
+/**
+ * Get the note message
+ *
+ * @param note the note
+ * @return the note message
+ */
+GIT_EXTERN(const char *) git_note_message(const git_note *note);
+
+
+/**
+ * Get the note object's id
+ *
+ * @param note the note
+ * @return the note object's id
+ */
+GIT_EXTERN(const git_oid *) git_note_id(const git_note *note);
+
+/**
+ * Add a note for an object
+ *
+ * @param out pointer to store the OID (optional); NULL in case of error
+ * @param repo repository where to store the note
+ * @param notes_ref canonical name of the reference to use (optional);
+ * defaults to "refs/notes/commits"
+ * @param author signature of the notes commit author
+ * @param committer signature of the notes commit committer
+ * @param oid OID of the git object to decorate
+ * @param note Content of the note to add for object oid
+ * @param force Overwrite existing note
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_create(
+ git_oid *out,
+ git_repository *repo,
+ const char *notes_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const git_oid *oid,
+ const char *note,
+ int force);
+
+
+/**
+ * Remove the note for an object
+ *
+ * @param repo repository where the note lives
+ * @param notes_ref canonical name of the reference to use (optional);
+ * defaults to "refs/notes/commits"
+ * @param author signature of the notes commit author
+ * @param committer signature of the notes commit committer
+ * @param oid OID of the git object to remove the note from
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_remove(
+ git_repository *repo,
+ const char *notes_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const git_oid *oid);
+
+/**
+ * Free a git_note object
+ *
+ * @param note git_note object
+ */
+GIT_EXTERN(void) git_note_free(git_note *note);
+
+/**
+ * Get the default notes reference for a repository
+ *
+ * @param out buffer in which to store the name of the default notes reference
+ * @param repo The Git repository
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_default_ref(git_buf *out, git_repository *repo);
+
+/**
+ * Loop over all the notes within a specified namespace
+ * and issue a callback for each one.
+ *
+ * @param repo Repository where to find the notes.
+ *
+ * @param notes_ref Reference to read from (optional); defaults to
+ * "refs/notes/commits".
+ *
+ * @param note_cb Callback to invoke per found annotation. Return non-zero
+ * to stop looping.
+ *
+ * @param payload Extra parameter to callback function.
+ *
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_note_foreach(
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_object_h__
+#define INCLUDE_git_object_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "buffer.h"
+
+/**
+ * @file git2/object.h
+ * @brief Git revision object management routines
+ * @defgroup git_object Git revision object management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a reference to one of the objects in a repository.
+ *
+ * The generated reference is owned by the repository and
+ * should be closed with the `git_object_free` method
+ * instead of free'd manually.
+ *
+ * The 'type' parameter must match the type of the object
+ * in the odb; the method will fail otherwise.
+ * The special value 'GIT_OBJ_ANY' may be passed to let
+ * the method guess the object's type.
+ *
+ * @param object pointer to the looked-up object
+ * @param repo the repository to look up the object
+ * @param id the unique identifier for the object
+ * @param type the type of the object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_object_lookup(
+ git_object **object,
+ git_repository *repo,
+ const git_oid *id,
+ git_otype type);
+
+/**
+ * Lookup a reference to one of the objects in a repository,
+ * given a prefix of its identifier (short id).
+ *
+ * The object obtained will be so that its identifier
+ * matches the first 'len' hexadecimal characters
+ * (packets of 4 bits) of the given 'id'.
+ * 'len' must be at least GIT_OID_MINPREFIXLEN, and
+ * long enough to identify a unique object matching
+ * the prefix; otherwise the method will fail.
+ *
+ * The generated reference is owned by the repository and
+ * should be closed with the `git_object_free` method
+ * instead of free'd manually.
+ *
+ * The 'type' parameter must match the type of the object
+ * in the odb; the method will fail otherwise.
+ * The special value 'GIT_OBJ_ANY' may be passed to let
+ * the method guess the object's type.
+ *
+ * @param object_out pointer where to store the looked-up object
+ * @param repo the repository to look up the object
+ * @param id a short identifier for the object
+ * @param len the length of the short identifier
+ * @param type the type of the object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_object_lookup_prefix(
+ git_object **object_out,
+ git_repository *repo,
+ const git_oid *id,
+ size_t len,
+ git_otype type);
+
+
+/**
+ * Lookup an object that represents a tree entry.
+ *
+ * @param out buffer that receives a pointer to the object (which must be freed
+ * by the caller)
+ * @param treeish root object that can be peeled to a tree
+ * @param path relative path from the root object to the desired object
+ * @param type type of object desired
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_object_lookup_bypath(
+ git_object **out,
+ const git_object *treeish,
+ const char *path,
+ git_otype type);
+
+/**
+ * Get the id (SHA1) of a repository object
+ *
+ * @param obj the repository object
+ * @return the SHA1 id
+ */
+GIT_EXTERN(const git_oid *) git_object_id(const git_object *obj);
+
+/**
+ * Get a short abbreviated OID string for the object
+ *
+ * This starts at the "core.abbrev" length (default 7 characters) and
+ * iteratively extends to a longer string if that length is ambiguous.
+ * The result will be unambiguous (at least until new objects are added to
+ * the repository).
+ *
+ * @param out Buffer to write string into
+ * @param obj The object to get an ID for
+ * @return 0 on success, <0 for error
+ */
+GIT_EXTERN(int) git_object_short_id(git_buf *out, const git_object *obj);
+
+/**
+ * Get the object type of an object
+ *
+ * @param obj the repository object
+ * @return the object's type
+ */
+GIT_EXTERN(git_otype) git_object_type(const git_object *obj);
+
+/**
+ * Get the repository that owns this object
+ *
+ * Freeing or calling `git_repository_close` on the
+ * returned pointer will invalidate the actual object.
+ *
+ * Any other operation may be run on the repository without
+ * affecting the object.
+ *
+ * @param obj the object
+ * @return the repository who owns this object
+ */
+GIT_EXTERN(git_repository *) git_object_owner(const git_object *obj);
+
+/**
+ * Close an open object
+ *
+ * This method instructs the library to close an existing
+ * object; note that git_objects are owned and cached by the repository
+ * so the object may or may not be freed after this library call,
+ * depending on how aggressive is the caching mechanism used
+ * by the repository.
+ *
+ * IMPORTANT:
+ * It *is* necessary to call this method when you stop using
+ * an object. Failure to do so will cause a memory leak.
+ *
+ * @param object the object to close
+ */
+GIT_EXTERN(void) git_object_free(git_object *object);
+
+/**
+ * Convert an object type to its string representation.
+ *
+ * The result is a pointer to a string in static memory and
+ * should not be free()'ed.
+ *
+ * @param type object type to convert.
+ * @return the corresponding string representation.
+ */
+GIT_EXTERN(const char *) git_object_type2string(git_otype type);
+
+/**
+ * Convert a string object type representation to it's git_otype.
+ *
+ * @param str the string to convert.
+ * @return the corresponding git_otype.
+ */
+GIT_EXTERN(git_otype) git_object_string2type(const char *str);
+
+/**
+ * Determine if the given git_otype is a valid loose object type.
+ *
+ * @param type object type to test.
+ * @return true if the type represents a valid loose object type,
+ * false otherwise.
+ */
+GIT_EXTERN(int) git_object_typeisloose(git_otype type);
+
+/**
+ * Get the size in bytes for the structure which
+ * acts as an in-memory representation of any given
+ * object type.
+ *
+ * For all the core types, this would the equivalent
+ * of calling `sizeof(git_commit)` if the core types
+ * were not opaque on the external API.
+ *
+ * @param type object type to get its size
+ * @return size in bytes of the object
+ */
+GIT_EXTERN(size_t) git_object__size(git_otype type);
+
+/**
+ * Recursively peel an object until an object of the specified type is met.
+ *
+ * If the query cannot be satisfied due to the object model,
+ * GIT_EINVALIDSPEC will be returned (e.g. trying to peel a blob to a
+ * tree).
+ *
+ * If you pass `GIT_OBJ_ANY` as the target type, then the object will
+ * be peeled until the type changes. A tag will be peeled until the
+ * referenced object is no longer a tag, and a commit will be peeled
+ * to a tree. Any other object type will return GIT_EINVALIDSPEC.
+ *
+ * If peeling a tag we discover an object which cannot be peeled to
+ * the target type due to the object model, GIT_EPEEL will be
+ * returned.
+ *
+ * You must free the returned object.
+ *
+ * @param peeled Pointer to the peeled git_object
+ * @param object The object to be processed
+ * @param target_type The type of the requested object (a GIT_OBJ_ value)
+ * @return 0 on success, GIT_EINVALIDSPEC, GIT_EPEEL, or an error code
+ */
+GIT_EXTERN(int) git_object_peel(
+ git_object **peeled,
+ const git_object *object,
+ git_otype target_type);
+
+/**
+ * Create an in-memory copy of a Git object. The copy must be
+ * explicitly free'd or it will leak.
+ *
+ * @param dest Pointer to store the copy of the object
+ * @param source Original object to copy
+ */
+GIT_EXTERN(int) git_object_dup(git_object **dest, git_object *source);
+
+/** @} */
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_odb_h__
+#define INCLUDE_git_odb_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "oidarray.h"
+
+/**
+ * @file git2/odb.h
+ * @brief Git object database routines
+ * @defgroup git_odb Git object database routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Function type for callbacks from git_odb_foreach.
+ */
+typedef int (*git_odb_foreach_cb)(const git_oid *id, void *payload);
+
+/**
+ * Create a new object database with no backends.
+ *
+ * Before the ODB can be used for read/writing, a custom database
+ * backend must be manually added using `git_odb_add_backend()`
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_new(git_odb **out);
+
+/**
+ * Create a new object database and automatically add
+ * the two default backends:
+ *
+ * - git_odb_backend_loose: read and write loose object files
+ * from disk, assuming `objects_dir` as the Objects folder
+ *
+ * - git_odb_backend_pack: read objects from packfiles,
+ * assuming `objects_dir` as the Objects folder which
+ * contains a 'pack/' folder with the corresponding data
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @param objects_dir path of the backends' "objects" directory.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_open(git_odb **out, const char *objects_dir);
+
+/**
+ * Add an on-disk alternate to an existing Object DB.
+ *
+ * Note that the added path must point to an `objects`, not
+ * to a full repository, to use it as an alternate store.
+ *
+ * Alternate backends are always checked for objects *after*
+ * all the main backends have been exhausted.
+ *
+ * Writing is disabled on alternate backends.
+ *
+ * @param odb database to add the backend to
+ * @param path path to the objects folder for the alternate
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_add_disk_alternate(git_odb *odb, const char *path);
+
+/**
+ * Close an open object database.
+ *
+ * @param db database pointer to close. If NULL no action is taken.
+ */
+GIT_EXTERN(void) git_odb_free(git_odb *db);
+
+/**
+ * Read an object from the database.
+ *
+ * This method queries all available ODB backends
+ * trying to read the given OID.
+ *
+ * The returned object is reference counted and
+ * internally cached, so it should be closed
+ * by the user once it's no longer in use.
+ *
+ * @param out pointer where to store the read object
+ * @param db database to search for the object in.
+ * @param id identity of the object to read.
+ * @return
+ * - 0 if the object was read;
+ * - GIT_ENOTFOUND if the object is not in the database.
+ */
+GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id);
+
+/**
+ * Read an object from the database, given a prefix
+ * of its identifier.
+ *
+ * This method queries all available ODB backends
+ * trying to match the 'len' first hexadecimal
+ * characters of the 'short_id'.
+ * The remaining (GIT_OID_HEXSZ-len)*4 bits of
+ * 'short_id' must be 0s.
+ * 'len' must be at least GIT_OID_MINPREFIXLEN,
+ * and the prefix must be long enough to identify
+ * a unique object in all the backends; the
+ * method will fail otherwise.
+ *
+ * The returned object is reference counted and
+ * internally cached, so it should be closed
+ * by the user once it's no longer in use.
+ *
+ * @param out pointer where to store the read object
+ * @param db database to search for the object in.
+ * @param short_id a prefix of the id of the object to read.
+ * @param len the length of the prefix
+ * @return
+ * - 0 if the object was read;
+ * - GIT_ENOTFOUND if the object is not in the database.
+ * - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix)
+ */
+GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len);
+
+/**
+ * Read the header of an object from the database, without
+ * reading its full contents.
+ *
+ * The header includes the length and the type of an object.
+ *
+ * Note that most backends do not support reading only the header
+ * of an object, so the whole object will be read and then the
+ * header will be returned.
+ *
+ * @param len_out pointer where to store the length
+ * @param type_out pointer where to store the type
+ * @param db database to search for the object in.
+ * @param id identity of the object to read.
+ * @return
+ * - 0 if the object was read;
+ * - GIT_ENOTFOUND if the object is not in the database.
+ */
+GIT_EXTERN(int) git_odb_read_header(size_t *len_out, git_otype *type_out, git_odb *db, const git_oid *id);
+
+/**
+ * Determine if the given object can be found in the object database.
+ *
+ * @param db database to be searched for the given object.
+ * @param id the object to search for.
+ * @return
+ * - 1, if the object was found
+ * - 0, otherwise
+ */
+GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id);
+
+/**
+ * Determine if an object can be found in the object database by an
+ * abbreviated object ID.
+ *
+ * @param out The full OID of the found object if just one is found.
+ * @param db The database to be searched for the given object.
+ * @param short_id A prefix of the id of the object to read.
+ * @param len The length of the prefix.
+ * @return 0 if found, GIT_ENOTFOUND if not found, GIT_EAMBIGUOUS if multiple
+ * matches were found, other value < 0 if there was a read error.
+ */
+GIT_EXTERN(int) git_odb_exists_prefix(
+ git_oid *out, git_odb *db, const git_oid *short_id, size_t len);
+
+/**
+ * The information about object IDs to query in `git_odb_expand_ids`,
+ * which will be populated upon return.
+ */
+typedef struct git_odb_expand_id {
+ /** The object ID to expand */
+ git_oid id;
+
+ /**
+ * The length of the object ID (in nibbles, or packets of 4 bits; the
+ * number of hex characters)
+ * */
+ unsigned short length;
+
+ /**
+ * The (optional) type of the object to search for; leave as `0` or set
+ * to `GIT_OBJ_ANY` to query for any object matching the ID.
+ */
+ git_otype type;
+} git_odb_expand_id;
+
+/**
+ * Determine if one or more objects can be found in the object database
+ * by their abbreviated object ID and type. The given array will be
+ * updated in place: for each abbreviated ID that is unique in the
+ * database, and of the given type (if specified), the full object ID,
+ * object ID length (`GIT_OID_HEXSZ`) and type will be written back to
+ * the array. For IDs that are not found (or are ambiguous), the
+ * array entry will be zeroed.
+ *
+ * Note that since this function operates on multiple objects, the
+ * underlying database will not be asked to be reloaded if an object is
+ * not found (which is unlike other object database operations.)
+ *
+ * @param db The database to be searched for the given objects.
+ * @param ids An array of short object IDs to search for
+ * @param count The length of the `ids` array
+ * @return 0 on success or an error code on failure
+ */
+GIT_EXTERN(int) git_odb_expand_ids(
+ git_odb *db,
+ git_odb_expand_id *ids,
+ size_t count);
+
+/**
+ * Refresh the object database to load newly added files.
+ *
+ * If the object databases have changed on disk while the library
+ * is running, this function will force a reload of the underlying
+ * indexes.
+ *
+ * Use this function when you're confident that an external
+ * application has tampered with the ODB.
+ *
+ * NOTE that it is not necessary to call this function at all. The
+ * library will automatically attempt to refresh the ODB
+ * when a lookup fails, to see if the looked up object exists
+ * on disk but hasn't been loaded yet.
+ *
+ * @param db database to refresh
+ * @return 0 on success, error code otherwise
+ */
+GIT_EXTERN(int) git_odb_refresh(struct git_odb *db);
+
+/**
+ * List all objects available in the database
+ *
+ * The callback will be called for each object available in the
+ * database. Note that the objects are likely to be returned in the index
+ * order, which would make accessing the objects in that order inefficient.
+ * Return a non-zero value from the callback to stop looping.
+ *
+ * @param db database to use
+ * @param cb the callback to call for each object
+ * @param payload data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload);
+
+/**
+ * Write an object directly into the ODB
+ *
+ * This method writes a full object straight into the ODB.
+ * For most cases, it is preferred to write objects through a write
+ * stream, which is both faster and less memory intensive, specially
+ * for big objects.
+ *
+ * This method is provided for compatibility with custom backends
+ * which are not able to support streaming writes
+ *
+ * @param out pointer to store the OID result of the write
+ * @param odb object database where to store the object
+ * @param data buffer with the data to store
+ * @param len size of the buffer
+ * @param type type of the data to store
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size_t len, git_otype type);
+
+/**
+ * Open a stream to write an object into the ODB
+ *
+ * The type and final length of the object must be specified
+ * when opening the stream.
+ *
+ * The returned stream will be of type `GIT_STREAM_WRONLY`, and it
+ * won't be effective until `git_odb_stream_finalize_write` is called
+ * and returns without an error
+ *
+ * The stream must always be freed when done with `git_odb_stream_free` or
+ * will leak memory.
+ *
+ * @see git_odb_stream
+ *
+ * @param out pointer where to store the stream
+ * @param db object database where the stream will write
+ * @param size final size of the object that will be written
+ * @param type type of the object that will be written
+ * @return 0 if the stream was created; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, git_off_t size, git_otype type);
+
+/**
+ * Write to an odb stream
+ *
+ * This method will fail if the total number of received bytes exceeds the
+ * size declared with `git_odb_open_wstream()`
+ *
+ * @param stream the stream
+ * @param buffer the data to write
+ * @param len the buffer's length
+ * @return 0 if the write succeeded; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len);
+
+/**
+ * Finish writing to an odb stream
+ *
+ * The object will take its final name and will be available to the
+ * odb.
+ *
+ * This method will fail if the total number of received bytes
+ * differs from the size declared with `git_odb_open_wstream()`
+ *
+ * @param out pointer to store the resulting object's id
+ * @param stream the stream
+ * @return 0 on success; an error code otherwise
+ */
+GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream);
+
+/**
+ * Read from an odb stream
+ *
+ * Most backends don't implement streaming reads
+ */
+GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len);
+
+/**
+ * Free an odb stream
+ *
+ * @param stream the stream to free
+ */
+GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream);
+
+/**
+ * Open a stream to read an object from the ODB
+ *
+ * Note that most backends do *not* support streaming reads
+ * because they store their objects as compressed/delta'ed blobs.
+ *
+ * It's recommended to use `git_odb_read` instead, which is
+ * assured to work on all backends.
+ *
+ * The returned stream will be of type `GIT_STREAM_RDONLY` and
+ * will have the following methods:
+ *
+ * - stream->read: read `n` bytes from the stream
+ * - stream->free: free the stream
+ *
+ * The stream must always be free'd or will leak memory.
+ *
+ * @see git_odb_stream
+ *
+ * @param out pointer where to store the stream
+ * @param db object database where the stream will read from
+ * @param oid oid of the object the stream will read from
+ * @return 0 if the stream was created; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **out, git_odb *db, const git_oid *oid);
+
+/**
+ * Open a stream for writing a pack file to the ODB.
+ *
+ * If the ODB layer understands pack files, then the given
+ * packfile will likely be streamed directly to disk (and a
+ * corresponding index created). If the ODB layer does not
+ * understand pack files, the objects will be stored in whatever
+ * format the ODB layer uses.
+ *
+ * @see git_odb_writepack
+ *
+ * @param out pointer to the writepack functions
+ * @param db object database where the stream will read from
+ * @param progress_cb function to call with progress information.
+ * Be aware that this is called inline with network and indexing operations,
+ * so performance may be affected.
+ * @param progress_payload payload for the progress callback
+ */
+GIT_EXTERN(int) git_odb_write_pack(
+ git_odb_writepack **out,
+ git_odb *db,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload);
+
+/**
+ * Determine the object-ID (sha1 hash) of a data buffer
+ *
+ * The resulting SHA-1 OID will be the identifier for the data
+ * buffer as if the data buffer it were to written to the ODB.
+ *
+ * @param out the resulting object-ID.
+ * @param data data to hash
+ * @param len size of the data
+ * @param type of the data to hash
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_otype type);
+
+/**
+ * Read a file from disk and fill a git_oid with the object id
+ * that the file would have if it were written to the Object
+ * Database as an object of the given type (w/o applying filters).
+ * Similar functionality to git.git's `git hash-object` without
+ * the `-w` flag, however, with the --no-filters flag.
+ * If you need filters, see git_repository_hashfile.
+ *
+ * @param out oid structure the result is written into.
+ * @param path file to read and determine object id for
+ * @param type the type of the object that will be hashed
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type);
+
+/**
+ * Create a copy of an odb_object
+ *
+ * The returned copy must be manually freed with `git_odb_object_free`.
+ * Note that because of an implementation detail, the returned copy will be
+ * the same pointer as `source`: the object is internally refcounted, so the
+ * copy still needs to be freed twice.
+ *
+ * @param dest pointer where to store the copy
+ * @param source object to copy
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_object_dup(git_odb_object **dest, git_odb_object *source);
+
+/**
+ * Close an ODB object
+ *
+ * This method must always be called once a `git_odb_object` is no
+ * longer needed, otherwise memory will leak.
+ *
+ * @param object object to close
+ */
+GIT_EXTERN(void) git_odb_object_free(git_odb_object *object);
+
+/**
+ * Return the OID of an ODB object
+ *
+ * This is the OID from which the object was read from
+ *
+ * @param object the object
+ * @return a pointer to the OID
+ */
+GIT_EXTERN(const git_oid *) git_odb_object_id(git_odb_object *object);
+
+/**
+ * Return the data of an ODB object
+ *
+ * This is the uncompressed, raw data as read from the ODB,
+ * without the leading header.
+ *
+ * This pointer is owned by the object and shall not be free'd.
+ *
+ * @param object the object
+ * @return a pointer to the data
+ */
+GIT_EXTERN(const void *) git_odb_object_data(git_odb_object *object);
+
+/**
+ * Return the size of an ODB object
+ *
+ * This is the real size of the `data` buffer, not the
+ * actual size of the object.
+ *
+ * @param object the object
+ * @return the size
+ */
+GIT_EXTERN(size_t) git_odb_object_size(git_odb_object *object);
+
+/**
+ * Return the type of an ODB object
+ *
+ * @param object the object
+ * @return the type
+ */
+GIT_EXTERN(git_otype) git_odb_object_type(git_odb_object *object);
+
+/**
+ * Add a custom backend to an existing Object DB
+ *
+ * The backends are checked in relative ordering, based on the
+ * value of the `priority` parameter.
+ *
+ * Read <odb_backends.h> for more information.
+ *
+ * @param odb database to add the backend to
+ * @param backend pointer to a git_odb_backend instance
+ * @param priority Value for ordering the backends queue
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority);
+
+/**
+ * Add a custom backend to an existing Object DB; this
+ * backend will work as an alternate.
+ *
+ * Alternate backends are always checked for objects *after*
+ * all the main backends have been exhausted.
+ *
+ * The backends are checked in relative ordering, based on the
+ * value of the `priority` parameter.
+ *
+ * Writing is disabled on alternate backends.
+ *
+ * Read <odb_backends.h> for more information.
+ *
+ * @param odb database to add the backend to
+ * @param backend pointer to a git_odb_backend instance
+ * @param priority Value for ordering the backends queue
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority);
+
+/**
+ * Get the number of ODB backend objects
+ *
+ * @param odb object database
+ * @return number of backends in the ODB
+ */
+GIT_EXTERN(size_t) git_odb_num_backends(git_odb *odb);
+
+/**
+ * Lookup an ODB backend object by index
+ *
+ * @param out output pointer to ODB backend at pos
+ * @param odb object database
+ * @param pos index into object database backend list
+ * @return 0 on success; GIT_ENOTFOUND if pos is invalid; other errors < 0
+ */
+GIT_EXTERN(int) git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_odb_backend_h__
+#define INCLUDE_git_odb_backend_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/backend.h
+ * @brief Git custom backend functions
+ * @defgroup git_odb Git object database routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/*
+ * Constructors for in-box ODB backends.
+ */
+
+/**
+ * Create a backend for the packfiles.
+ *
+ * @param out location to store the odb backend pointer
+ * @param objects_dir the Git repository's objects directory
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_dir);
+
+/**
+ * Create a backend for loose objects
+ *
+ * @param out location to store the odb backend pointer
+ * @param objects_dir the Git repository's objects directory
+ * @param compression_level zlib compression level to use
+ * @param do_fsync whether to do an fsync() after writing (currently ignored)
+ * @param dir_mode permissions to use creating a directory or 0 for defaults
+ * @param file_mode permissions to use creating a file or 0 for defaults
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_loose(
+ git_odb_backend **out,
+ const char *objects_dir,
+ int compression_level,
+ int do_fsync,
+ unsigned int dir_mode,
+ unsigned int file_mode);
+
+/**
+ * Create a backend out of a single packfile
+ *
+ * This can be useful for inspecting the contents of a single
+ * packfile.
+ *
+ * @param out location to store the odb backend pointer
+ * @param index_file path to the packfile's .idx file
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **out, const char *index_file);
+
+/** Streaming mode */
+typedef enum {
+ GIT_STREAM_RDONLY = (1 << 1),
+ GIT_STREAM_WRONLY = (1 << 2),
+ GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY),
+} git_odb_stream_t;
+
+/**
+ * A stream to read/write from a backend.
+ *
+ * This represents a stream of data being written to or read from a
+ * backend. When writing, the frontend functions take care of
+ * calculating the object's id and all `finalize_write` needs to do is
+ * store the object with the id it is passed.
+ */
+struct git_odb_stream {
+ git_odb_backend *backend;
+ unsigned int mode;
+ void *hash_ctx;
+
+ git_off_t declared_size;
+ git_off_t received_bytes;
+
+ /**
+ * Write at most `len` bytes into `buffer` and advance the stream.
+ */
+ int (*read)(git_odb_stream *stream, char *buffer, size_t len);
+
+ /**
+ * Write `len` bytes from `buffer` into the stream.
+ */
+ int (*write)(git_odb_stream *stream, const char *buffer, size_t len);
+
+ /**
+ * Store the contents of the stream as an object with the id
+ * specified in `oid`.
+ *
+ * This method might not be invoked if:
+ * - an error occurs earlier with the `write` callback,
+ * - the object referred to by `oid` already exists in any backend, or
+ * - the final number of received bytes differs from the size declared
+ * with `git_odb_open_wstream()`
+ */
+ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid);
+
+ /**
+ * Free the stream's memory.
+ *
+ * This method might be called without a call to `finalize_write` if
+ * an error occurs or if the object is already present in the ODB.
+ */
+ void (*free)(git_odb_stream *stream);
+};
+
+/** A stream to write a pack file to the ODB */
+struct git_odb_writepack {
+ git_odb_backend *backend;
+
+ int (*append)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);
+ int (*commit)(git_odb_writepack *writepack, git_transfer_progress *stats);
+ void (*free)(git_odb_writepack *writepack);
+};
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_oid_h__
+#define INCLUDE_git_oid_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/oid.h
+ * @brief Git object id routines
+ * @defgroup git_oid Git object id routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Size (in bytes) of a raw/binary oid */
+#define GIT_OID_RAWSZ 20
+
+/** Size (in bytes) of a hex formatted oid */
+#define GIT_OID_HEXSZ (GIT_OID_RAWSZ * 2)
+
+/** Minimum length (in number of hex characters,
+ * i.e. packets of 4 bits) of an oid prefix */
+#define GIT_OID_MINPREFIXLEN 4
+
+/** Unique identity of any object (commit, tree, blob, tag). */
+typedef struct git_oid {
+ /** raw binary formatted id */
+ unsigned char id[GIT_OID_RAWSZ];
+} git_oid;
+
+/**
+ * Parse a hex formatted object id into a git_oid.
+ *
+ * @param out oid structure the result is written into.
+ * @param str input hex string; must be pointing at the start of
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (40 bytes).
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str);
+
+/**
+ * Parse a hex formatted null-terminated string into a git_oid.
+ *
+ * @param out oid structure the result is written into.
+ * @param str input hex string; must be at least 4 characters
+ * long and null-terminated.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str);
+
+/**
+ * Parse N characters of a hex formatted object id into a git_oid
+ *
+ * If N is odd, N-1 characters will be parsed instead.
+ * The remaining space in the git_oid will be set to zero.
+ *
+ * @param out oid structure the result is written into.
+ * @param str input hex string of at least size `length`
+ * @param length length of the input string
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length);
+
+/**
+ * Copy an already raw oid into a git_oid structure.
+ *
+ * @param out oid structure the result is written into.
+ * @param raw the raw input bytes to be copied.
+ */
+GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw);
+
+/**
+ * Format a git_oid into a hex string.
+ *
+ * @param out output hex string; must be pointing at the start of
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (40 bytes). Only the
+ * oid digits are written; a '\\0' terminator must be added
+ * by the caller if it is required.
+ * @param id oid structure to format.
+ */
+GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
+
+/**
+ * Format a git_oid into a partial hex string.
+ *
+ * @param out output hex string; you say how many bytes to write.
+ * If the number of bytes is > GIT_OID_HEXSZ, extra bytes
+ * will be zeroed; if not, a '\0' terminator is NOT added.
+ * @param n number of characters to write into out string
+ * @param id oid structure to format.
+ */
+GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id);
+
+/**
+ * Format a git_oid into a loose-object path string.
+ *
+ * The resulting string is "aa/...", where "aa" is the first two
+ * hex digits of the oid and "..." is the remaining 38 digits.
+ *
+ * @param out output hex string; must be pointing at the start of
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (41 bytes). Only the
+ * oid digits are written; a '\\0' terminator must be added
+ * by the caller if it is required.
+ * @param id oid structure to format.
+ */
+GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id);
+
+/**
+ * Format a git_oid into a statically allocated c-string.
+ *
+ * The c-string is owned by the library and should not be freed
+ * by the user. If libgit2 is built with thread support, the string
+ * will be stored in TLS (i.e. one buffer per thread) to allow for
+ * concurrent calls of the function.
+ *
+ * @param oid The oid structure to format
+ * @return the c-string
+ */
+GIT_EXTERN(char *) git_oid_tostr_s(const git_oid *oid);
+
+/**
+ * Format a git_oid into a buffer as a hex format c-string.
+ *
+ * If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting
+ * oid c-string will be truncated to n-1 characters (but will still be
+ * NUL-byte terminated).
+ *
+ * If there are any input parameter errors (out == NULL, n == 0, oid ==
+ * NULL), then a pointer to an empty string is returned, so that the
+ * return value can always be printed.
+ *
+ * @param out the buffer into which the oid string is output.
+ * @param n the size of the out buffer.
+ * @param id the oid structure to format.
+ * @return the out buffer pointer, assuming no input parameter
+ * errors, otherwise a pointer to an empty string.
+ */
+GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *id);
+
+/**
+ * Copy an oid from one structure to another.
+ *
+ * @param out oid structure the result is written into.
+ * @param src oid structure to copy from.
+ */
+GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src);
+
+/**
+ * Compare two oid structures.
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return <0, 0, >0 if a < b, a == b, a > b.
+ */
+GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b);
+
+/**
+ * Compare two oid structures for equality
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return true if equal, false otherwise
+ */
+GIT_EXTERN(int) git_oid_equal(const git_oid *a, const git_oid *b);
+
+/**
+ * Compare the first 'len' hexadecimal characters (packets of 4 bits)
+ * of two oid structures.
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @param len the number of hex chars to compare
+ * @return 0 in case of a match
+ */
+GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len);
+
+/**
+ * Check if an oid equals an hex formatted object id.
+ *
+ * @param id oid structure.
+ * @param str input hex string of an object id.
+ * @return 0 in case of a match, -1 otherwise.
+ */
+GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str);
+
+/**
+ * Compare an oid to an hex formatted object id.
+ *
+ * @param id oid structure.
+ * @param str input hex string of an object id.
+ * @return -1 if str is not valid, <0 if id sorts before str,
+ * 0 if id matches str, >0 if id sorts after str.
+ */
+GIT_EXTERN(int) git_oid_strcmp(const git_oid *id, const char *str);
+
+/**
+ * Check is an oid is all zeros.
+ *
+ * @return 1 if all zeros, 0 otherwise.
+ */
+GIT_EXTERN(int) git_oid_iszero(const git_oid *id);
+
+/**
+ * OID Shortener object
+ */
+typedef struct git_oid_shorten git_oid_shorten;
+
+/**
+ * Create a new OID shortener.
+ *
+ * The OID shortener is used to process a list of OIDs
+ * in text form and return the shortest length that would
+ * uniquely identify all of them.
+ *
+ * E.g. look at the result of `git log --abbrev`.
+ *
+ * @param min_length The minimal length for all identifiers,
+ * which will be used even if shorter OIDs would still
+ * be unique.
+ * @return a `git_oid_shorten` instance, NULL if OOM
+ */
+GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length);
+
+/**
+ * Add a new OID to set of shortened OIDs and calculate
+ * the minimal length to uniquely identify all the OIDs in
+ * the set.
+ *
+ * The OID is expected to be a 40-char hexadecimal string.
+ * The OID is owned by the user and will not be modified
+ * or freed.
+ *
+ * For performance reasons, there is a hard-limit of how many
+ * OIDs can be added to a single set (around ~32000, assuming
+ * a mostly randomized distribution), which should be enough
+ * for any kind of program, and keeps the algorithm fast and
+ * memory-efficient.
+ *
+ * Attempting to add more than those OIDs will result in a
+ * GITERR_INVALID error
+ *
+ * @param os a `git_oid_shorten` instance
+ * @param text_id an OID in text form
+ * @return the minimal length to uniquely identify all OIDs
+ * added so far to the set; or an error code (<0) if an
+ * error occurs.
+ */
+GIT_EXTERN(int) git_oid_shorten_add(git_oid_shorten *os, const char *text_id);
+
+/**
+ * Free an OID shortener instance
+ *
+ * @param os a `git_oid_shorten` instance
+ */
+GIT_EXTERN(void) git_oid_shorten_free(git_oid_shorten *os);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_oidarray_h__
+#define INCLUDE_git_oidarray_h__
+
+#include "common.h"
+#include "oid.h"
+
+GIT_BEGIN_DECL
+
+/** Array of object ids */
+typedef struct git_oidarray {
+ git_oid *ids;
+ size_t count;
+} git_oidarray;
+
+/**
+ * Free the OID array
+ *
+ * This method must (and must only) be called on `git_oidarray`
+ * objects where the array is allocated by the library. Not doing so,
+ * will result in a memory leak.
+ *
+ * This does not free the `git_oidarray` itself, since the library will
+ * never allocate that object directly itself (it is more commonly embedded
+ * inside another struct or created on the stack).
+ *
+ * @param array git_oidarray from which to free oid data
+ */
+GIT_EXTERN(void) git_oidarray_free(git_oidarray *array);
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_pack_h__
+#define INCLUDE_git_pack_h__
+
+#include "common.h"
+#include "oid.h"
+
+/**
+ * @file git2/pack.h
+ * @brief Git pack management routines
+ *
+ * Packing objects
+ * ---------------
+ *
+ * Creation of packfiles requires two steps:
+ *
+ * - First, insert all the objects you want to put into the packfile
+ * using `git_packbuilder_insert` and `git_packbuilder_insert_tree`.
+ * It's important to add the objects in recency order ("in the order
+ * that they are 'reachable' from head").
+ *
+ * "ANY order will give you a working pack, ... [but it is] the thing
+ * that gives packs good locality. It keeps the objects close to the
+ * head (whether they are old or new, but they are _reachable_ from the
+ * head) at the head of the pack. So packs actually have absolutely
+ * _wonderful_ IO patterns." - Linus Torvalds
+ * git.git/Documentation/technical/pack-heuristics.txt
+ *
+ * - Second, use `git_packbuilder_write` or `git_packbuilder_foreach` to
+ * write the resulting packfile.
+ *
+ * libgit2 will take care of the delta ordering and generation.
+ * `git_packbuilder_set_threads` can be used to adjust the number of
+ * threads used for the process.
+ *
+ * See tests/pack/packbuilder.c for an example.
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Stages that are reported by the packbuilder progress callback.
+ */
+typedef enum {
+ GIT_PACKBUILDER_ADDING_OBJECTS = 0,
+ GIT_PACKBUILDER_DELTAFICATION = 1,
+} git_packbuilder_stage_t;
+
+/**
+ * Initialize a new packbuilder
+ *
+ * @param out The new packbuilder object
+ * @param repo The repository
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_new(git_packbuilder **out, git_repository *repo);
+
+/**
+ * Set number of threads to spawn
+ *
+ * By default, libgit2 won't spawn any threads at all;
+ * when set to 0, libgit2 will autodetect the number of
+ * CPUs.
+ *
+ * @param pb The packbuilder
+ * @param n Number of threads to spawn
+ * @return number of actual threads to be used
+ */
+GIT_EXTERN(unsigned int) git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);
+
+/**
+ * Insert a single object
+ *
+ * For an optimal pack it's mandatory to insert objects in recency order,
+ * commits followed by trees and blobs.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the commit
+ * @param name The name; might be NULL
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert(git_packbuilder *pb, const git_oid *id, const char *name);
+
+/**
+ * Insert a root tree object
+ *
+ * This will add the tree as well as all referenced trees and blobs.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the root tree
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *id);
+
+/**
+ * Insert a commit object
+ *
+ * This will add a commit as well as the completed referenced tree.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the commit
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *id);
+
+/**
+ * Insert objects as given by the walk
+ *
+ * Those commits and all objects they reference will be inserted into
+ * the packbuilder.
+ *
+ * @param pb the packbuilder
+ * @param walk the revwalk to use to fill the packbuilder
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk);
+
+/**
+ * Recursively insert an object and its referenced objects
+ *
+ * Insert the object as well as any object it references.
+ *
+ * @param pb the packbuilder
+ * @param id the id of the root object to insert
+ * @param name optional name for the object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name);
+
+/**
+ * Write the contents of the packfile to an in-memory buffer
+ *
+ * The contents of the buffer will become a valid packfile, even though there
+ * will be no attached index
+ *
+ * @param buf Buffer where to write the packfile
+ * @param pb The packbuilder
+ */
+GIT_EXTERN(int) git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
+
+/**
+ * Write the new pack and corresponding index file to path.
+ *
+ * @param pb The packbuilder
+ * @param path to the directory where the packfile and index should be stored
+ * @param mode permissions to use creating a packfile or 0 for defaults
+ * @param progress_cb function to call with progress information from the indexer (optional)
+ * @param progress_cb_payload payload for the progress callback (optional)
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_write(
+ git_packbuilder *pb,
+ const char *path,
+ unsigned int mode,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload);
+
+/**
+* Get the packfile's hash
+*
+* A packfile's name is derived from the sorted hashing of all object
+* names. This is only correct after the packfile has been written.
+*
+* @param pb The packbuilder object
+*/
+GIT_EXTERN(const git_oid *) git_packbuilder_hash(git_packbuilder *pb);
+
+typedef int (*git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload);
+
+/**
+ * Create the new pack and pass each object to the callback
+ *
+ * @param pb the packbuilder
+ * @param cb the callback to call with each packed object's buffer
+ * @param payload the callback's data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_foreach_cb cb, void *payload);
+
+/**
+ * Get the total number of objects the packbuilder will write out
+ *
+ * @param pb the packbuilder
+ * @return the number of objects in the packfile
+ */
+GIT_EXTERN(size_t) git_packbuilder_object_count(git_packbuilder *pb);
+
+/**
+ * Get the number of objects the packbuilder has already written out
+ *
+ * @param pb the packbuilder
+ * @return the number of objects which have already been written
+ */
+GIT_EXTERN(size_t) git_packbuilder_written(git_packbuilder *pb);
+
+/** Packbuilder progress notification function */
+typedef int (*git_packbuilder_progress)(
+ int stage,
+ uint32_t current,
+ uint32_t total,
+ void *payload);
+
+/**
+ * Set the callbacks for a packbuilder
+ *
+ * @param pb The packbuilder object
+ * @param progress_cb Function to call with progress information during
+ * pack building. Be aware that this is called inline with pack building
+ * operations, so performance may be affected.
+ * @param progress_cb_payload Payload for progress callback.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_set_callbacks(
+ git_packbuilder *pb,
+ git_packbuilder_progress progress_cb,
+ void *progress_cb_payload);
+
+/**
+ * Free the packbuilder and all associated data
+ *
+ * @param pb The packbuilder
+ */
+GIT_EXTERN(void) git_packbuilder_free(git_packbuilder *pb);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_patch_h__
+#define INCLUDE_git_patch_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "diff.h"
+
+/**
+ * @file git2/patch.h
+ * @brief Patch handling routines.
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * The diff patch is used to store all the text diffs for a delta.
+ *
+ * You can easily loop over the content of patches and get information about
+ * them.
+ */
+typedef struct git_patch git_patch;
+
+/**
+ * Return a patch for an entry in the diff list.
+ *
+ * The `git_patch` is a newly created object contains the text diffs
+ * for the delta. You have to call `git_patch_free()` when you are
+ * done with it. You can use the patch object to loop over all the hunks
+ * and lines in the diff of the one delta.
+ *
+ * For an unchanged file or a binary file, no `git_patch` will be
+ * created, the output will be set to NULL, and the `binary` flag will be
+ * set true in the `git_diff_delta` structure.
+ *
+ * It is okay to pass NULL for either of the output parameters; if you pass
+ * NULL for the `git_patch`, then the text diff will not be calculated.
+ *
+ * @param out Output parameter for the delta patch object
+ * @param diff Diff list object
+ * @param idx Index into diff list
+ * @return 0 on success, other value < 0 on error
+ */
+GIT_EXTERN(int) git_patch_from_diff(
+ git_patch **out, git_diff *diff, size_t idx);
+
+/**
+ * Directly generate a patch from the difference between two blobs.
+ *
+ * This is just like `git_diff_blobs()` except it generates a patch object
+ * for the difference instead of directly making callbacks. You can use the
+ * standard `git_patch` accessor functions to read the patch data, and
+ * you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param new_blob Blob for new side of diff, or NULL for empty blob
+ * @param new_as_path Treat new blob as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_blobs(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const git_blob *new_blob,
+ const char *new_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Directly generate a patch from the difference between a blob and a buffer.
+ *
+ * This is just like `git_diff_blob_to_buffer()` except it generates a patch
+ * object for the difference instead of directly making callbacks. You can
+ * use the standard `git_patch` accessor functions to read the patch
+ * data, and you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param buffer Raw data for new side of diff, or NULL for empty
+ * @param buffer_len Length of raw data for new side of diff
+ * @param buffer_as_path Treat buffer as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_blob_and_buffer(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const char *buffer,
+ size_t buffer_len,
+ const char *buffer_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Directly generate a patch from the difference between two buffers.
+ *
+ * This is just like `git_diff_buffers()` except it generates a patch
+ * object for the difference instead of directly making callbacks. You can
+ * use the standard `git_patch` accessor functions to read the patch
+ * data, and you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_buffers(
+ git_patch **out,
+ const void *old_buffer,
+ size_t old_len,
+ const char *old_as_path,
+ const char *new_buffer,
+ size_t new_len,
+ const char *new_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Free a git_patch object.
+ */
+GIT_EXTERN(void) git_patch_free(git_patch *patch);
+
+/**
+ * Get the delta associated with a patch. This delta points to internal
+ * data and you do not have to release it when you are done with it.
+ */
+GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(const git_patch *patch);
+
+/**
+ * Get the number of hunks in a patch
+ */
+GIT_EXTERN(size_t) git_patch_num_hunks(const git_patch *patch);
+
+/**
+ * Get line counts of each type in a patch.
+ *
+ * This helps imitate a diff --numstat type of output. For that purpose,
+ * you only need the `total_additions` and `total_deletions` values, but we
+ * include the `total_context` line count in case you want the total number
+ * of lines of diff output that will be generated.
+ *
+ * All outputs are optional. Pass NULL if you don't need a particular count.
+ *
+ * @param total_context Count of context lines in output, can be NULL.
+ * @param total_additions Count of addition lines in output, can be NULL.
+ * @param total_deletions Count of deletion lines in output, can be NULL.
+ * @param patch The git_patch object
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_patch_line_stats(
+ size_t *total_context,
+ size_t *total_additions,
+ size_t *total_deletions,
+ const git_patch *patch);
+
+/**
+ * Get the information about a hunk in a patch
+ *
+ * Given a patch and a hunk index into the patch, this returns detailed
+ * information about that hunk. Any of the output pointers can be passed
+ * as NULL if you don't care about that particular piece of information.
+ *
+ * @param out Output pointer to git_diff_hunk of hunk
+ * @param lines_in_hunk Output count of total lines in this hunk
+ * @param patch Input pointer to patch object
+ * @param hunk_idx Input index of hunk to get information about
+ * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error
+ */
+GIT_EXTERN(int) git_patch_get_hunk(
+ const git_diff_hunk **out,
+ size_t *lines_in_hunk,
+ git_patch *patch,
+ size_t hunk_idx);
+
+/**
+ * Get the number of lines in a hunk.
+ *
+ * @param patch The git_patch object
+ * @param hunk_idx Index of the hunk
+ * @return Number of lines in hunk or GIT_ENOTFOUND if invalid hunk index
+ */
+GIT_EXTERN(int) git_patch_num_lines_in_hunk(
+ const git_patch *patch,
+ size_t hunk_idx);
+
+/**
+ * Get data about a line in a hunk of a patch.
+ *
+ * Given a patch, a hunk index, and a line index in the hunk, this
+ * will return a lot of details about that line. If you pass a hunk
+ * index larger than the number of hunks or a line index larger than
+ * the number of lines in the hunk, this will return -1.
+ *
+ * @param out The git_diff_line data for this line
+ * @param patch The patch to look in
+ * @param hunk_idx The index of the hunk
+ * @param line_of_hunk The index of the line in the hunk
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_patch_get_line_in_hunk(
+ const git_diff_line **out,
+ git_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk);
+
+/**
+ * Look up size of patch diff data in bytes
+ *
+ * This returns the raw size of the patch data. This only includes the
+ * actual data from the lines of the diff, not the file or hunk headers.
+ *
+ * If you pass `include_context` as true (non-zero), this will be the size
+ * of all of the diff output; if you pass it as false (zero), this will
+ * only include the actual changed lines (as if `context_lines` was 0).
+ *
+ * @param patch A git_patch representing changes to one file
+ * @param include_context Include context lines in size if non-zero
+ * @param include_hunk_headers Include hunk header lines if non-zero
+ * @param include_file_headers Include file header lines if non-zero
+ * @return The number of bytes of data
+ */
+GIT_EXTERN(size_t) git_patch_size(
+ git_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers);
+
+/**
+ * Serialize the patch to text via callback.
+ *
+ * Returning a non-zero value from the callback will terminate the iteration
+ * and return that value to the caller.
+ *
+ * @param patch A git_patch representing changes to one file
+ * @param print_cb Callback function to output lines of the patch. Will be
+ * called for file headers, hunk headers, and diff lines.
+ * @param payload Reference pointer that will be passed to your callbacks.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_patch_print(
+ git_patch *patch,
+ git_diff_line_cb print_cb,
+ void *payload);
+
+/**
+ * Get the content of a patch as a single diff text.
+ *
+ * @param out The git_buf to be filled in
+ * @param patch A git_patch representing changes to one file
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_patch_to_buf(
+ git_buf *out,
+ git_patch *patch);
+
+GIT_END_DECL
+
+/**@}*/
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_pathspec_h__
+#define INCLUDE_git_pathspec_h__
+
+#include "common.h"
+#include "types.h"
+#include "strarray.h"
+#include "diff.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Compiled pathspec
+ */
+typedef struct git_pathspec git_pathspec;
+
+/**
+ * List of filenames matching a pathspec
+ */
+typedef struct git_pathspec_match_list git_pathspec_match_list;
+
+/**
+ * Options controlling how pathspec match should be executed
+ *
+ * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise
+ * match will use native case sensitivity of platform filesystem
+ * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise
+ * match will use native case sensitivity of platform filesystem
+ * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple
+ * string comparison for matching
+ * - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error
+ * code GIT_ENOTFOUND if no matches are found; otherwise no matches is
+ * still success (return 0) but `git_pathspec_match_list_entrycount`
+ * will indicate 0 matches.
+ * - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list`
+ * should track which patterns matched which files so that at the end of
+ * the match we can identify patterns that did not match any files.
+ * - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list`
+ * does not need to keep the actual matching filenames. Use this to
+ * just test if there were any matches at all or in combination with
+ * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec.
+ */
+typedef enum {
+ GIT_PATHSPEC_DEFAULT = 0,
+ GIT_PATHSPEC_IGNORE_CASE = (1u << 0),
+ GIT_PATHSPEC_USE_CASE = (1u << 1),
+ GIT_PATHSPEC_NO_GLOB = (1u << 2),
+ GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3),
+ GIT_PATHSPEC_FIND_FAILURES = (1u << 4),
+ GIT_PATHSPEC_FAILURES_ONLY = (1u << 5),
+} git_pathspec_flag_t;
+
+/**
+ * Compile a pathspec
+ *
+ * @param out Output of the compiled pathspec
+ * @param pathspec A git_strarray of the paths to match
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_pathspec_new(
+ git_pathspec **out, const git_strarray *pathspec);
+
+/**
+ * Free a pathspec
+ *
+ * @param ps The compiled pathspec
+ */
+GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps);
+
+/**
+ * Try to match a path against a pathspec
+ *
+ * Unlike most of the other pathspec matching functions, this will not
+ * fall back on the native case-sensitivity for your platform. You must
+ * explicitly pass flags to control case sensitivity or else this will
+ * fall back on being case sensitive.
+ *
+ * @param ps The compiled pathspec
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param path The pathname to attempt to match
+ * @return 1 is path matches spec, 0 if it does not
+ */
+GIT_EXTERN(int) git_pathspec_matches_path(
+ const git_pathspec *ps, uint32_t flags, const char *path);
+
+/**
+ * Match a pathspec against the working directory of a repository.
+ *
+ * This matches the pathspec against the current files in the working
+ * directory of the repository. It is an error to invoke this on a bare
+ * repo. This handles git ignores (i.e. ignored files will not be
+ * considered to match the `pathspec` unless the file is tracked in the
+ * index).
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param repo The repository in which to match; bare repo is an error
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag was given
+ */
+GIT_EXTERN(int) git_pathspec_match_workdir(
+ git_pathspec_match_list **out,
+ git_repository *repo,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against entries in an index.
+ *
+ * This matches the pathspec against the files in the repository index.
+ *
+ * NOTE: At the moment, the case sensitivity of this match is controlled
+ * by the current case-sensitivity of the index object itself and the
+ * USE_CASE and IGNORE_CASE flags will have no effect. This behavior will
+ * be corrected in a future release.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param index The index to match against
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_index(
+ git_pathspec_match_list **out,
+ git_index *index,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against files in a tree.
+ *
+ * This matches the pathspec against the files in the given tree.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param tree The root-level tree to match against
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_tree(
+ git_pathspec_match_list **out,
+ git_tree *tree,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against files in a diff list.
+ *
+ * This matches the pathspec against the files in the given diff list.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param diff A generated diff list
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_diff(
+ git_pathspec_match_list **out,
+ git_diff *diff,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Free memory associates with a git_pathspec_match_list
+ *
+ * @param m The git_pathspec_match_list to be freed
+ */
+GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m);
+
+/**
+ * Get the number of items in a match list.
+ *
+ * @param m The git_pathspec_match_list object
+ * @return Number of items in match list
+ */
+GIT_EXTERN(size_t) git_pathspec_match_list_entrycount(
+ const git_pathspec_match_list *m);
+
+/**
+ * Get a matching filename by position.
+ *
+ * This routine cannot be used if the match list was generated by
+ * `git_pathspec_match_diff`. If so, it will always return NULL.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the list
+ * @return The filename of the match
+ */
+GIT_EXTERN(const char *) git_pathspec_match_list_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+/**
+ * Get a matching diff delta by position.
+ *
+ * This routine can only be used if the match list was generated by
+ * `git_pathspec_match_diff`. Otherwise it will always return NULL.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the list
+ * @return The filename of the match
+ */
+GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+/**
+ * Get the number of pathspec items that did not match.
+ *
+ * This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when
+ * generating the git_pathspec_match_list.
+ *
+ * @param m The git_pathspec_match_list object
+ * @return Number of items in original pathspec that had no matches
+ */
+GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount(
+ const git_pathspec_match_list *m);
+
+/**
+ * Get an original pathspec string that had no matches.
+ *
+ * This will be return NULL for positions out of range.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the failed items
+ * @return The pathspec pattern that didn't match anything
+ */
+GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_proxy_h__
+#define INCLUDE_git_proxy_h__
+
+#include "common.h"
+#include "transport.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * The type of proxy to use.
+ */
+typedef enum {
+ /**
+ * Do not attempt to connect through a proxy
+ *
+ * If built against libcurl, it itself may attempt to connect
+ * to a proxy if the environment variables specify it.
+ */
+ GIT_PROXY_NONE,
+ /**
+ * Try to auto-detect the proxy from the git configuration.
+ */
+ GIT_PROXY_AUTO,
+ /**
+ * Connect via the URL given in the options
+ */
+ GIT_PROXY_SPECIFIED,
+} git_proxy_t;
+
+/**
+ * Options for connecting through a proxy
+ *
+ * Note that not all types may be supported, depending on the platform
+ * and compilation options.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * The type of proxy to use, by URL, auto-detect.
+ */
+ git_proxy_t type;
+
+ /**
+ * The URL of the proxy.
+ */
+ const char *url;
+
+ /**
+ * This will be called if the remote host requires
+ * authentication in order to connect to it.
+ *
+ * Returning GIT_PASSTHROUGH will make libgit2 behave as
+ * though this field isn't set.
+ */
+ git_cred_acquire_cb credentials;
+
+ /**
+ * If cert verification fails, this will be called to let the
+ * user make the final decision of whether to allow the
+ * connection to proceed. Returns 1 to allow the connection, 0
+ * to disallow it or a negative value to indicate an error.
+ */
+ git_transport_certificate_check_cb certificate_check;
+
+ /**
+ * Payload to be provided to the credentials and certificate
+ * check callbacks.
+ */
+ void *payload;
+} git_proxy_options;
+
+#define GIT_PROXY_OPTIONS_VERSION 1
+#define GIT_PROXY_OPTIONS_INIT {GIT_PROXY_OPTIONS_VERSION}
+
+/**
+ * Initialize a proxy options structure
+ *
+ * @param opts the options struct to initialize
+ * @param version the version of the struct, use `GIT_PROXY_OPTIONS_VERSION`
+ */
+GIT_EXTERN(int) git_proxy_init_options(git_proxy_options *opts, unsigned int version);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_rebase_h__
+#define INCLUDE_git_rebase_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "annotated_commit.h"
+
+/**
+ * @file git2/rebase.h
+ * @brief Git rebase routines
+ * @defgroup git_rebase Git merge routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Rebase options
+ *
+ * Use to tell the rebase machinery how to operate.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Used by `git_rebase_init`, this will instruct other clients working
+ * on this rebase that you want a quiet rebase experience, which they
+ * may choose to provide in an application-specific manner. This has no
+ * effect upon libgit2 directly, but is provided for interoperability
+ * between Git tools.
+ */
+ int quiet;
+
+ /**
+ * Used by `git_rebase_init`, this will begin an in-memory rebase,
+ * which will allow callers to step through the rebase operations and
+ * commit the rebased changes, but will not rewind HEAD or update the
+ * repository to be in a rebasing state. This will not interfere with
+ * the working directory (if there is one).
+ */
+ int inmemory;
+
+ /**
+ * Used by `git_rebase_finish`, this is the name of the notes reference
+ * used to rewrite notes for rebased commits when finishing the rebase;
+ * if NULL, the contents of the configuration option `notes.rewriteRef`
+ * is examined, unless the configuration option `notes.rewrite.rebase`
+ * is set to false. If `notes.rewriteRef` is also NULL, notes will
+ * not be rewritten.
+ */
+ const char *rewrite_notes_ref;
+
+ /**
+ * Options to control how trees are merged during `git_rebase_next`.
+ */
+ git_merge_options merge_options;
+
+ /**
+ * Options to control how files are written during `git_rebase_init`,
+ * `git_rebase_next` and `git_rebase_abort`. Note that a minimum
+ * strategy of `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`,
+ * and a minimum strategy of `GIT_CHECKOUT_FORCE` is defaulted in
+ * `abort` to match git semantics.
+ */
+ git_checkout_options checkout_options;
+} git_rebase_options;
+
+/**
+ * Type of rebase operation in-progress after calling `git_rebase_next`.
+ */
+typedef enum {
+ /**
+ * The given commit is to be cherry-picked. The client should commit
+ * the changes and continue if there are no conflicts.
+ */
+ GIT_REBASE_OPERATION_PICK = 0,
+
+ /**
+ * The given commit is to be cherry-picked, but the client should prompt
+ * the user to provide an updated commit message.
+ */
+ GIT_REBASE_OPERATION_REWORD,
+
+ /**
+ * The given commit is to be cherry-picked, but the client should stop
+ * to allow the user to edit the changes before committing them.
+ */
+ GIT_REBASE_OPERATION_EDIT,
+
+ /**
+ * The given commit is to be squashed into the previous commit. The
+ * commit message will be merged with the previous message.
+ */
+ GIT_REBASE_OPERATION_SQUASH,
+
+ /**
+ * The given commit is to be squashed into the previous commit. The
+ * commit message from this commit will be discarded.
+ */
+ GIT_REBASE_OPERATION_FIXUP,
+
+ /**
+ * No commit will be cherry-picked. The client should run the given
+ * command and (if successful) continue.
+ */
+ GIT_REBASE_OPERATION_EXEC,
+} git_rebase_operation_t;
+
+#define GIT_REBASE_OPTIONS_VERSION 1
+#define GIT_REBASE_OPTIONS_INIT \
+ { GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \
+ GIT_CHECKOUT_OPTIONS_INIT}
+
+/** Indicates that a rebase operation is not (yet) in progress. */
+#define GIT_REBASE_NO_OPERATION SIZE_MAX
+
+/**
+ * A rebase operation
+ *
+ * Describes a single instruction/operation to be performed during the
+ * rebase.
+ */
+typedef struct {
+ /** The type of rebase operation. */
+ git_rebase_operation_t type;
+
+ /**
+ * The commit ID being cherry-picked. This will be populated for
+ * all operations except those of type `GIT_REBASE_OPERATION_EXEC`.
+ */
+ const git_oid id;
+
+ /**
+ * The executable the user has requested be run. This will only
+ * be populated for operations of type `GIT_REBASE_OPERATION_EXEC`.
+ */
+ const char *exec;
+} git_rebase_operation;
+
+/**
+ * Initializes a `git_rebase_options` with default values. Equivalent to
+ * creating an instance with GIT_REBASE_OPTIONS_INIT.
+ *
+ * @param opts the `git_rebase_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_REBASE_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_init_options(
+ git_rebase_options *opts,
+ unsigned int version);
+
+/**
+ * Initializes a rebase operation to rebase the changes in `branch`
+ * relative to `upstream` onto another branch. To begin the rebase
+ * process, call `git_rebase_next`. When you have finished with this
+ * object, call `git_rebase_free`.
+ *
+ * @param out Pointer to store the rebase object
+ * @param repo The repository to perform the rebase
+ * @param branch The terminal commit to rebase, or NULL to rebase the
+ * current branch
+ * @param upstream The commit to begin rebasing from, or NULL to rebase all
+ * reachable commits
+ * @param onto The branch to rebase onto, or NULL to rebase onto the given
+ * upstream
+ * @param opts Options to specify how rebase is performed, or NULL
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_init(
+ git_rebase **out,
+ git_repository *repo,
+ const git_annotated_commit *branch,
+ const git_annotated_commit *upstream,
+ const git_annotated_commit *onto,
+ const git_rebase_options *opts);
+
+/**
+ * Opens an existing rebase that was previously started by either an
+ * invocation of `git_rebase_init` or by another client.
+ *
+ * @param out Pointer to store the rebase object
+ * @param repo The repository that has a rebase in-progress
+ * @param opts Options to specify how rebase is performed
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_open(
+ git_rebase **out,
+ git_repository *repo,
+ const git_rebase_options *opts);
+
+/**
+ * Gets the count of rebase operations that are to be applied.
+ *
+ * @param rebase The in-progress rebase
+ * @return The number of rebase operations in total
+ */
+GIT_EXTERN(size_t) git_rebase_operation_entrycount(git_rebase *rebase);
+
+/**
+ * Gets the index of the rebase operation that is currently being applied.
+ * If the first operation has not yet been applied (because you have
+ * called `init` but not yet `next`) then this returns
+ * `GIT_REBASE_NO_OPERATION`.
+ *
+ * @param rebase The in-progress rebase
+ * @return The index of the rebase operation currently being applied.
+ */
+GIT_EXTERN(size_t) git_rebase_operation_current(git_rebase *rebase);
+
+/**
+ * Gets the rebase operation specified by the given index.
+ *
+ * @param rebase The in-progress rebase
+ * @param idx The index of the rebase operation to retrieve
+ * @return The rebase operation or NULL if `idx` was out of bounds
+ */
+GIT_EXTERN(git_rebase_operation *) git_rebase_operation_byindex(
+ git_rebase *rebase,
+ size_t idx);
+
+/**
+ * Performs the next rebase operation and returns the information about it.
+ * If the operation is one that applies a patch (which is any operation except
+ * GIT_REBASE_OPERATION_EXEC) then the patch will be applied and the index and
+ * working directory will be updated with the changes. If there are conflicts,
+ * you will need to address those before committing the changes.
+ *
+ * @param operation Pointer to store the rebase operation that is to be performed next
+ * @param rebase The rebase in progress
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_next(
+ git_rebase_operation **operation,
+ git_rebase *rebase);
+
+/**
+ * Gets the index produced by the last operation, which is the result
+ * of `git_rebase_next` and which will be committed by the next
+ * invocation of `git_rebase_commit`. This is useful for resolving
+ * conflicts in an in-memory rebase before committing them. You must
+ * call `git_index_free` when you are finished with this.
+ *
+ * This is only applicable for in-memory rebases; for rebases within
+ * a working directory, the changes were applied to the repository's
+ * index.
+ */
+GIT_EXTERN(int) git_rebase_inmemory_index(
+ git_index **index,
+ git_rebase *rebase);
+
+/**
+ * Commits the current patch. You must have resolved any conflicts that
+ * were introduced during the patch application from the `git_rebase_next`
+ * invocation.
+ *
+ * @param id Pointer in which to store the OID of the newly created commit
+ * @param rebase The rebase that is in-progress
+ * @param author The author of the updated commit, or NULL to keep the
+ * author from the original commit
+ * @param committer The committer of the rebase
+ * @param message_encoding The encoding for the message in the commit,
+ * represented with a standard encoding name. If message is NULL,
+ * this should also be NULL, and the encoding from the original
+ * commit will be maintained. If message is specified, this may be
+ * NULL to indicate that "UTF-8" is to be used.
+ * @param message The message for this commit, or NULL to use the message
+ * from the original commit.
+ * @return Zero on success, GIT_EUNMERGED if there are unmerged changes in
+ * the index, GIT_EAPPLIED if the current commit has already
+ * been applied to the upstream and there is nothing to commit,
+ * -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_commit(
+ git_oid *id,
+ git_rebase *rebase,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message);
+
+/**
+ * Aborts a rebase that is currently in progress, resetting the repository
+ * and working directory to their state before rebase began.
+ *
+ * @param rebase The rebase that is in-progress
+ * @return Zero on success; GIT_ENOTFOUND if a rebase is not in progress,
+ * -1 on other errors.
+ */
+GIT_EXTERN(int) git_rebase_abort(git_rebase *rebase);
+
+/**
+ * Finishes a rebase that is currently in progress once all patches have
+ * been applied.
+ *
+ * @param rebase The rebase that is in-progress
+ * @param signature The identity that is finishing the rebase (optional)
+ * @return Zero on success; -1 on error
+ */
+GIT_EXTERN(int) git_rebase_finish(
+ git_rebase *rebase,
+ const git_signature *signature);
+
+/**
+ * Frees the `git_rebase` object.
+ *
+ * @param rebase The rebase object
+ */
+GIT_EXTERN(void) git_rebase_free(git_rebase *rebase);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_refdb_h__
+#define INCLUDE_git_refdb_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "refs.h"
+
+/**
+ * @file git2/refdb.h
+ * @brief Git custom refs backend functions
+ * @defgroup git_refdb Git custom refs backend API
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new reference database with no backends.
+ *
+ * Before the Ref DB can be used for read/writing, a custom database
+ * backend must be manually set using `git_refdb_set_backend()`
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_refdb_new(git_refdb **out, git_repository *repo);
+
+/**
+ * Create a new reference database and automatically add
+ * the default backends:
+ *
+ * - git_refdb_dir: read and write loose and packed refs
+ * from disk, assuming the repository dir as the folder
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_refdb_open(git_refdb **out, git_repository *repo);
+
+/**
+ * Suggests that the given refdb compress or optimize its references.
+ * This mechanism is implementation specific. For on-disk reference
+ * databases, for example, this may pack all loose references.
+ */
+GIT_EXTERN(int) git_refdb_compress(git_refdb *refdb);
+
+/**
+ * Close an open reference database.
+ *
+ * @param refdb reference database pointer or NULL
+ */
+GIT_EXTERN(void) git_refdb_free(git_refdb *refdb);
+
+/** @} */
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_reflog_h__
+#define INCLUDE_git_reflog_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+/**
+ * @file git2/reflog.h
+ * @brief Git reflog management routines
+ * @defgroup git_reflog Git reflog management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Read the reflog for the given reference
+ *
+ * If there is no reflog file for the given
+ * reference yet, an empty reflog object will
+ * be returned.
+ *
+ * The reflog must be freed manually by using
+ * git_reflog_free().
+ *
+ * @param out pointer to reflog
+ * @param repo the repostiory
+ * @param name reference to look up
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_read(git_reflog **out, git_repository *repo, const char *name);
+
+/**
+ * Write an existing in-memory reflog object back to disk
+ * using an atomic file lock.
+ *
+ * @param reflog an existing reflog object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_write(git_reflog *reflog);
+
+/**
+ * Add a new entry to the in-memory reflog.
+ *
+ * `msg` is optional and can be NULL.
+ *
+ * @param reflog an existing reflog object
+ * @param id the OID the reference is now pointing to
+ * @param committer the signature of the committer
+ * @param msg the reflog message
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg);
+
+/**
+ * Rename a reflog
+ *
+ * The reflog to be renamed is expected to already exist
+ *
+ * The new name will be checked for validity.
+ * See `git_reference_create_symbolic()` for rules about valid names.
+ *
+ * @param repo the repository
+ * @param old_name the old name of the reference
+ * @param name the new name of the reference
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name);
+
+/**
+ * Delete the reflog for the given reference
+ *
+ * @param repo the repository
+ * @param name the reflog to delete
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_delete(git_repository *repo, const char *name);
+
+/**
+ * Get the number of log entries in a reflog
+ *
+ * @param reflog the previously loaded reflog
+ * @return the number of log entries
+ */
+GIT_EXTERN(size_t) git_reflog_entrycount(git_reflog *reflog);
+
+/**
+ * Lookup an entry by its index
+ *
+ * Requesting the reflog entry with an index of 0 (zero) will
+ * return the most recently created entry.
+ *
+ * @param reflog a previously loaded reflog
+ * @param idx the position of the entry to lookup. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
+ * @return the entry; NULL if not found
+ */
+GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(const git_reflog *reflog, size_t idx);
+
+/**
+ * Remove an entry from the reflog by its index
+ *
+ * To ensure there's no gap in the log history, set `rewrite_previous_entry`
+ * param value to 1. When deleting entry `n`, member old_oid of entry `n-1`
+ * (if any) will be updated with the value of member new_oid of entry `n+1`.
+ *
+ * @param reflog a previously loaded reflog.
+ *
+ * @param idx the position of the entry to remove. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
+ *
+ * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise.
+ *
+ * @return 0 on success, GIT_ENOTFOUND if the entry doesn't exist
+ * or an error code.
+ */
+GIT_EXTERN(int) git_reflog_drop(
+ git_reflog *reflog,
+ size_t idx,
+ int rewrite_previous_entry);
+
+/**
+ * Get the old oid
+ *
+ * @param entry a reflog entry
+ * @return the old oid
+ */
+GIT_EXTERN(const git_oid *) git_reflog_entry_id_old(const git_reflog_entry *entry);
+
+/**
+ * Get the new oid
+ *
+ * @param entry a reflog entry
+ * @return the new oid at this time
+ */
+GIT_EXTERN(const git_oid *) git_reflog_entry_id_new(const git_reflog_entry *entry);
+
+/**
+ * Get the committer of this entry
+ *
+ * @param entry a reflog entry
+ * @return the committer
+ */
+GIT_EXTERN(const git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry);
+
+/**
+ * Get the log message
+ *
+ * @param entry a reflog entry
+ * @return the log msg
+ */
+GIT_EXTERN(const char *) git_reflog_entry_message(const git_reflog_entry *entry);
+
+/**
+ * Free the reflog
+ *
+ * @param reflog reflog to free
+ */
+GIT_EXTERN(void) git_reflog_free(git_reflog *reflog);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_refs_h__
+#define INCLUDE_git_refs_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "strarray.h"
+
+/**
+ * @file git2/refs.h
+ * @brief Git reference management routines
+ * @defgroup git_reference Git reference management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a reference by name in a repository.
+ *
+ * The returned reference must be freed by the user.
+ *
+ * The name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param out pointer to the looked-up reference
+ * @param repo the repository to look up the reference
+ * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name);
+
+/**
+ * Lookup a reference by name and resolve immediately to OID.
+ *
+ * This function provides a quick way to resolve a reference name straight
+ * through to the object id that it refers to. This avoids having to
+ * allocate or free any `git_reference` objects for simple situations.
+ *
+ * The name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param out Pointer to oid to be filled in
+ * @param repo The repository in which to look up the reference
+ * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_reference_name_to_id(
+ git_oid *out, git_repository *repo, const char *name);
+
+/**
+ * Lookup a reference by DWIMing its short name
+ *
+ * Apply the git precendence rules to the given shorthand to determine
+ * which reference the user is referring to.
+ *
+ * @param out pointer in which to store the reference
+ * @param repo the repository in which to look
+ * @param shorthand the short name for the reference
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand);
+
+/**
+ * Conditionally create a new symbolic reference.
+ *
+ * A symbolic reference is a reference name that refers to another
+ * reference name. If the other name moves, the symbolic name will move,
+ * too. As a simple example, the "HEAD" reference might refer to
+ * "refs/heads/master" while on the "master" branch of a repository.
+ *
+ * The symbolic reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The message for the reflog will be ignored if the reference does
+ * not belong in the standard set (HEAD, branches and remote-tracking
+ * branches) and it does not have a reflog.
+ *
+ * It will return GIT_EMODIFIED if the reference's value at the time
+ * of updating does not match the one passed through `current_value`
+ * (i.e. if the ref has changed since the user read it).
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param target The target of the reference
+ * @param force Overwrite existing references
+ * @param current_value The expected value of the reference when updating
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC, GIT_EMODIFIED or an error code
+ */
+GIT_EXTERN(int) git_reference_symbolic_create_matching(git_reference **out, git_repository *repo, const char *name, const char *target, int force, const char *current_value, const char *log_message);
+
+/**
+ * Create a new symbolic reference.
+ *
+ * A symbolic reference is a reference name that refers to another
+ * reference name. If the other name moves, the symbolic name will move,
+ * too. As a simple example, the "HEAD" reference might refer to
+ * "refs/heads/master" while on the "master" branch of a repository.
+ *
+ * The symbolic reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The message for the reflog will be ignored if the reference does
+ * not belong in the standard set (HEAD, branches and remote-tracking
+ * branches) and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param target The target of the reference
+ * @param force Overwrite existing references
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force, const char *log_message);
+
+/**
+ * Create a new direct reference.
+ *
+ * A direct reference (also called an object id reference) refers directly
+ * to a specific object id (a.k.a. OID or SHA) in the repository. The id
+ * permanently refers to the object (although the reference itself can be
+ * moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0"
+ * refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977.
+ *
+ * The direct reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The message for the reflog will be ignored if the reference does
+ * not belong in the standard set (HEAD, branches and remote-tracking
+ * branches) and and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param id The object id pointed to by the reference.
+ * @param force Overwrite existing references
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const char *log_message);
+
+/**
+ * Conditionally create new direct reference
+ *
+ * A direct reference (also called an object id reference) refers directly
+ * to a specific object id (a.k.a. OID or SHA) in the repository. The id
+ * permanently refers to the object (although the reference itself can be
+ * moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0"
+ * refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977.
+ *
+ * The direct reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The message for the reflog will be ignored if the reference does
+ * not belong in the standard set (HEAD, branches and remote-tracking
+ * branches) and and it does not have a reflog.
+ *
+ * It will return GIT_EMODIFIED if the reference's value at the time
+ * of updating does not match the one passed through `current_id`
+ * (i.e. if the ref has changed since the user read it).
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param id The object id pointed to by the reference.
+ * @param force Overwrite existing references
+ * @param current_id The expected value of the reference at the time of update
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EMODIFIED if the value of the reference
+ * has changed, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reference_create_matching(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_oid *current_id, const char *log_message);
+
+/**
+ * Get the OID pointed to by a direct reference.
+ *
+ * Only available if the reference is direct (i.e. an object id reference,
+ * not a symbolic one).
+ *
+ * To find the OID of a symbolic ref, call `git_reference_resolve()` and
+ * then this function (or maybe use `git_reference_name_to_id()` to
+ * directly resolve a reference name all the way through to an OID).
+ *
+ * @param ref The reference
+ * @return a pointer to the oid if available, NULL otherwise
+ */
+GIT_EXTERN(const git_oid *) git_reference_target(const git_reference *ref);
+
+/**
+ * Return the peeled OID target of this reference.
+ *
+ * This peeled OID only applies to direct references that point to
+ * a hard Tag object: it is the result of peeling such Tag.
+ *
+ * @param ref The reference
+ * @return a pointer to the oid if available, NULL otherwise
+ */
+GIT_EXTERN(const git_oid *) git_reference_target_peel(const git_reference *ref);
+
+/**
+ * Get full name to the reference pointed to by a symbolic reference.
+ *
+ * Only available if the reference is symbolic.
+ *
+ * @param ref The reference
+ * @return a pointer to the name if available, NULL otherwise
+ */
+GIT_EXTERN(const char *) git_reference_symbolic_target(const git_reference *ref);
+
+/**
+ * Get the type of a reference.
+ *
+ * Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC)
+ *
+ * @param ref The reference
+ * @return the type
+ */
+GIT_EXTERN(git_ref_t) git_reference_type(const git_reference *ref);
+
+/**
+ * Get the full name of a reference.
+ *
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param ref The reference
+ * @return the full name for the ref
+ */
+GIT_EXTERN(const char *) git_reference_name(const git_reference *ref);
+
+/**
+ * Resolve a symbolic reference to a direct reference.
+ *
+ * This method iteratively peels a symbolic reference until it resolves to
+ * a direct reference to an OID.
+ *
+ * The peeled reference is returned in the `resolved_ref` argument, and
+ * must be freed manually once it's no longer needed.
+ *
+ * If a direct reference is passed as an argument, a copy of that
+ * reference is returned. This copy must be manually freed too.
+ *
+ * @param out Pointer to the peeled reference
+ * @param ref The reference
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_resolve(git_reference **out, const git_reference *ref);
+
+/**
+ * Get the repository where a reference resides.
+ *
+ * @param ref The reference
+ * @return a pointer to the repo
+ */
+GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref);
+
+/**
+ * Create a new reference with the same name as the given reference but a
+ * different symbolic target. The reference must be a symbolic reference,
+ * otherwise this will fail.
+ *
+ * The new reference will be written to disk, overwriting the given reference.
+ *
+ * The target name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * The message for the reflog will be ignored if the reference does
+ * not belong in the standard set (HEAD, branches and remote-tracking
+ * branches) and and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
+ * @param ref The reference
+ * @param target The new target for the reference
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target,
+ const char *log_message);
+
+/**
+ * Conditionally create a new reference with the same name as the given reference but a
+ * different OID target. The reference must be a direct reference, otherwise
+ * this will fail.
+ *
+ * The new reference will be written to disk, overwriting the given reference.
+ *
+ * @param out Pointer to the newly created reference
+ * @param ref The reference
+ * @param id The new target OID for the reference
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EMODIFIED if the value of the reference
+ * has changed since it was read, or an error code
+ */
+GIT_EXTERN(int) git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id,
+ const char *log_message);
+
+/**
+ * Rename an existing reference.
+ *
+ * This method works for both direct and symbolic references.
+ *
+ * The new name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * If the `force` flag is not enabled, and there's already
+ * a reference with the given name, the renaming will fail.
+ *
+ * IMPORTANT:
+ * The user needs to write a proper reflog entry if the
+ * reflog is enabled for the repository. We only rename
+ * the reflog if it exists.
+ *
+ * @param ref The reference to rename
+ * @param new_name The new name for the reference
+ * @param force Overwrite an existing reference
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ *
+ */
+GIT_EXTERN(int) git_reference_rename(
+ git_reference **new_ref,
+ git_reference *ref,
+ const char *new_name,
+ int force,
+ const char *log_message);
+
+/**
+ * Delete an existing reference.
+ *
+ * This method works for both direct and symbolic references. The reference
+ * will be immediately removed on disk but the memory will not be freed.
+ * Callers must call `git_reference_free`.
+ *
+ * This function will return an error if the reference has changed
+ * from the time it was looked up.
+ *
+ * @param ref The reference to remove
+ * @return 0, GIT_EMODIFIED or an error code
+ */
+GIT_EXTERN(int) git_reference_delete(git_reference *ref);
+
+/**
+ * Delete an existing reference by name
+ *
+ * This method removes the named reference from the repository without
+ * looking at its old value.
+ *
+ * @param name The reference to remove
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_remove(git_repository *repo, const char *name);
+
+/**
+ * Fill a list with all the references that can be found in a repository.
+ *
+ * The string array will be filled with the names of all references; these
+ * values are owned by the user and should be free'd manually when no
+ * longer needed, using `git_strarray_free()`.
+ *
+ * @param array Pointer to a git_strarray structure where
+ * the reference names will be stored
+ * @param repo Repository where to find the refs
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo);
+
+typedef int (*git_reference_foreach_cb)(git_reference *reference, void *payload);
+typedef int (*git_reference_foreach_name_cb)(const char *name, void *payload);
+
+/**
+ * Perform a callback on each reference in the repository.
+ *
+ * The `callback` function will be called for each reference in the
+ * repository, receiving the reference object and the `payload` value
+ * passed to this method. Returning a non-zero value from the callback
+ * will terminate the iteration.
+ *
+ * @param repo Repository where to find the refs
+ * @param callback Function which will be called for every listed ref
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+/**
+ * Perform a callback on the fully-qualified name of each reference.
+ *
+ * The `callback` function will be called for each reference in the
+ * repository, receiving the name of the reference and the `payload` value
+ * passed to this method. Returning a non-zero value from the callback
+ * will terminate the iteration.
+ *
+ * @param repo Repository where to find the refs
+ * @param callback Function which will be called for every listed ref name
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload);
+
+/**
+ * Create a copy of an existing reference.
+ *
+ * Call `git_reference_free` to free the data.
+ *
+ * @param dest pointer where to store the copy
+ * @param source object to copy
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_dup(git_reference **dest, git_reference *source);
+
+/**
+ * Free the given reference.
+ *
+ * @param ref git_reference
+ */
+GIT_EXTERN(void) git_reference_free(git_reference *ref);
+
+/**
+ * Compare two references.
+ *
+ * @param ref1 The first git_reference
+ * @param ref2 The second git_reference
+ * @return 0 if the same, else a stable but meaningless ordering.
+ */
+GIT_EXTERN(int) git_reference_cmp(
+ const git_reference *ref1,
+ const git_reference *ref2);
+
+/**
+ * Create an iterator for the repo's references
+ *
+ * @param out pointer in which to store the iterator
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_iterator_new(
+ git_reference_iterator **out,
+ git_repository *repo);
+
+/**
+ * Create an iterator for the repo's references that match the
+ * specified glob
+ *
+ * @param out pointer in which to store the iterator
+ * @param repo the repository
+ * @param glob the glob to match against the reference names
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_iterator_glob_new(
+ git_reference_iterator **out,
+ git_repository *repo,
+ const char *glob);
+
+/**
+ * Get the next reference
+ *
+ * @param out pointer in which to store the reference
+ * @param iter the iterator
+ * @return 0, GIT_ITEROVER if there are no more; or an error code
+ */
+GIT_EXTERN(int) git_reference_next(git_reference **out, git_reference_iterator *iter);
+
+/**
+ * Get the next reference's name
+ *
+ * This function is provided for convenience in case only the names
+ * are interesting as it avoids the allocation of the `git_reference`
+ * object which `git_reference_next()` needs.
+ *
+ * @param out pointer in which to store the string
+ * @param iter the iterator
+ * @return 0, GIT_ITEROVER if there are no more; or an error code
+ */
+GIT_EXTERN(int) git_reference_next_name(const char **out, git_reference_iterator *iter);
+
+/**
+ * Free the iterator and its associated resources
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_reference_iterator_free(git_reference_iterator *iter);
+
+/**
+ * Perform a callback on each reference in the repository whose name
+ * matches the given pattern.
+ *
+ * This function acts like `git_reference_foreach()` with an additional
+ * pattern match being applied to the reference name before issuing the
+ * callback function. See that function for more information.
+ *
+ * The pattern is matched using fnmatch or "glob" style where a '*' matches
+ * any sequence of letters, a '?' matches any letter, and square brackets
+ * can be used to define character ranges (such as "[0-9]" for digits).
+ *
+ * @param repo Repository where to find the refs
+ * @param glob Pattern to match (fnmatch-style) against reference name.
+ * @param callback Function which will be called for every listed ref
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
+ */
+GIT_EXTERN(int) git_reference_foreach_glob(
+ git_repository *repo,
+ const char *glob,
+ git_reference_foreach_name_cb callback,
+ void *payload);
+
+/**
+ * Check if a reflog exists for the specified reference.
+ *
+ * @param repo the repository
+ * @param refname the reference's name
+ * @return 0 when no reflog can be found, 1 when it exists;
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_reference_has_log(git_repository *repo, const char *refname);
+
+/**
+ * Ensure there is a reflog for a particular reference.
+ *
+ * Make sure that successive updates to the reference will append to
+ * its log.
+ *
+ * @param repo the repository
+ * @param refname the reference's name
+ * @return 0 or an error code.
+ */
+GIT_EXTERN(int) git_reference_ensure_log(git_repository *repo, const char *refname);
+
+/**
+ * Check if a reference is a local branch.
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/heads
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_branch(const git_reference *ref);
+
+/**
+ * Check if a reference is a remote tracking branch
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/remotes
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_remote(const git_reference *ref);
+
+/**
+ * Check if a reference is a tag
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/tags
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_tag(const git_reference *ref);
+
+/**
+ * Check if a reference is a note
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/notes
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_note(const git_reference *ref);
+
+/**
+ * Normalization options for reference lookup
+ */
+typedef enum {
+ /**
+ * No particular normalization.
+ */
+ GIT_REF_FORMAT_NORMAL = 0u,
+
+ /**
+ * Control whether one-level refnames are accepted
+ * (i.e., refnames that do not contain multiple /-separated
+ * components). Those are expected to be written only using
+ * uppercase letters and underscore (FETCH_HEAD, ...)
+ */
+ GIT_REF_FORMAT_ALLOW_ONELEVEL = (1u << 0),
+
+ /**
+ * Interpret the provided name as a reference pattern for a
+ * refspec (as used with remote repositories). If this option
+ * is enabled, the name is allowed to contain a single * (<star>)
+ * in place of a one full pathname component
+ * (e.g., foo/<star>/bar but not foo/bar<star>).
+ */
+ GIT_REF_FORMAT_REFSPEC_PATTERN = (1u << 1),
+
+ /**
+ * Interpret the name as part of a refspec in shorthand form
+ * so the `ONELEVEL` naming rules aren't enforced and 'master'
+ * becomes a valid name.
+ */
+ GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1u << 2),
+} git_reference_normalize_t;
+
+/**
+ * Normalize reference name and check validity.
+ *
+ * This will normalize the reference name by removing any leading slash
+ * '/' characters and collapsing runs of adjacent slashes between name
+ * components into a single slash.
+ *
+ * Once normalized, if the reference name is valid, it will be returned in
+ * the user allocated buffer.
+ *
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param buffer_out User allocated buffer to store normalized name
+ * @param buffer_size Size of buffer_out
+ * @param name Reference name to be checked.
+ * @param flags Flags to constrain name validation rules - see the
+ * GIT_REF_FORMAT constants above.
+ * @return 0 on success, GIT_EBUFS if buffer is too small, GIT_EINVALIDSPEC
+ * or an error code.
+ */
+GIT_EXTERN(int) git_reference_normalize_name(
+ char *buffer_out,
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags);
+
+/**
+ * Recursively peel reference until object of the specified type is found.
+ *
+ * The retrieved `peeled` object is owned by the repository
+ * and should be closed with the `git_object_free` method.
+ *
+ * If you pass `GIT_OBJ_ANY` as the target type, then the object
+ * will be peeled until a non-tag object is met.
+ *
+ * @param out Pointer to the peeled git_object
+ * @param ref The reference to be processed
+ * @param type The type of the requested object (GIT_OBJ_COMMIT,
+ * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).
+ * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code
+ */
+GIT_EXTERN(int) git_reference_peel(
+ git_object **out,
+ git_reference *ref,
+ git_otype type);
+
+/**
+ * Ensure the reference name is well-formed.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * @param refname name to be checked.
+ * @return 1 if the reference name is acceptable; 0 if it isn't
+ */
+GIT_EXTERN(int) git_reference_is_valid_name(const char *refname);
+
+/**
+ * Get the reference's short name
+ *
+ * This will transform the reference name into a name "human-readable"
+ * version. If no shortname is appropriate, it will return the full
+ * name.
+ *
+ * The memory is owned by the reference and must not be freed.
+ *
+ * @param ref a reference
+ * @return the human-readable version of the name
+ */
+GIT_EXTERN(const char *) git_reference_shorthand(const git_reference *ref);
+
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_refspec_h__
+#define INCLUDE_git_refspec_h__
+
+#include "common.h"
+#include "types.h"
+#include "net.h"
+#include "buffer.h"
+
+/**
+ * @file git2/refspec.h
+ * @brief Git refspec attributes
+ * @defgroup git_refspec Git refspec attributes
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Get the source specifier
+ *
+ * @param refspec the refspec
+ * @return the refspec's source specifier
+ */
+GIT_EXTERN(const char *) git_refspec_src(const git_refspec *refspec);
+
+/**
+ * Get the destination specifier
+ *
+ * @param refspec the refspec
+ * @return the refspec's destination specifier
+ */
+GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec);
+
+/**
+ * Get the refspec's string
+ *
+ * @param refspec the refspec
+ * @returns the refspec's original string
+ */
+GIT_EXTERN(const char *) git_refspec_string(const git_refspec *refspec);
+
+/**
+ * Get the force update setting
+ *
+ * @param refspec the refspec
+ * @return 1 if force update has been set, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
+
+/**
+ * Get the refspec's direction.
+ *
+ * @param spec refspec
+ * @return GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH
+ */
+GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);
+
+/**
+ * Check if a refspec's source descriptor matches a reference
+ *
+ * @param refspec the refspec
+ * @param refname the name of the reference to check
+ * @return 1 if the refspec matches, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_src_matches(const git_refspec *refspec, const char *refname);
+
+/**
+ * Check if a refspec's destination descriptor matches a reference
+ *
+ * @param refspec the refspec
+ * @param refname the name of the reference to check
+ * @return 1 if the refspec matches, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_dst_matches(const git_refspec *refspec, const char *refname);
+
+/**
+ * Transform a reference to its target following the refspec's rules
+ *
+ * @param out where to store the target name
+ * @param spec the refspec
+ * @param name the name of the reference to transform
+ * @return 0, GIT_EBUFS or another error
+ */
+GIT_EXTERN(int) git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name);
+
+/**
+ * Transform a target reference to its source reference following the refspec's rules
+ *
+ * @param out where to store the source reference name
+ * @param spec the refspec
+ * @param name the name of the reference to transform
+ * @return 0, GIT_EBUFS or another error
+ */
+GIT_EXTERN(int) git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_remote_h__
+#define INCLUDE_git_remote_h__
+
+#include "common.h"
+#include "repository.h"
+#include "refspec.h"
+#include "net.h"
+#include "indexer.h"
+#include "strarray.h"
+#include "transport.h"
+#include "pack.h"
+#include "proxy.h"
+
+/**
+ * @file git2/remote.h
+ * @brief Git remote management functions
+ * @defgroup git_remote remote management functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Add a remote with the default fetch refspec to the repository's configuration.
+ *
+ * @param out the resulting remote
+ * @param repo the repository in which to create the remote
+ * @param name the remote's name
+ * @param url the remote's url
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+GIT_EXTERN(int) git_remote_create(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url);
+
+/**
+ * Add a remote with the provided fetch refspec (or default if NULL) to the repository's
+ * configuration.
+ *
+ * @param out the resulting remote
+ * @param repo the repository in which to create the remote
+ * @param name the remote's name
+ * @param url the remote's url
+ * @param fetch the remote fetch value
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+GIT_EXTERN(int) git_remote_create_with_fetchspec(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url,
+ const char *fetch);
+
+/**
+ * Create an anonymous remote
+ *
+ * Create a remote with the given url in-memory. You can use this when
+ * you have a URL instead of a remote's name.
+ *
+ * @param out pointer to the new remote objects
+ * @param repo the associated repository
+ * @param url the remote repository's URL
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_create_anonymous(
+ git_remote **out,
+ git_repository *repo,
+ const char *url);
+
+/**
+ * Get the information for a particular remote
+ *
+ * The name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out pointer to the new remote object
+ * @param repo the associated repository
+ * @param name the remote's name
+ * @return 0, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_remote_lookup(git_remote **out, git_repository *repo, const char *name);
+
+/**
+ * Create a copy of an existing remote. All internal strings are also
+ * duplicated. Callbacks are not duplicated.
+ *
+ * Call `git_remote_free` to free the data.
+ *
+ * @param dest pointer where to store the copy
+ * @param source object to copy
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_dup(git_remote **dest, git_remote *source);
+
+/**
+ * Get the remote's repository
+ *
+ * @param remote the remote
+ * @return a pointer to the repository
+ */
+GIT_EXTERN(git_repository *) git_remote_owner(const git_remote *remote);
+
+/**
+ * Get the remote's name
+ *
+ * @param remote the remote
+ * @return a pointer to the name or NULL for in-memory remotes
+ */
+GIT_EXTERN(const char *) git_remote_name(const git_remote *remote);
+
+/**
+ * Get the remote's url
+ *
+ * If url.*.insteadOf has been configured for this URL, it will
+ * return the modified URL.
+ *
+ * @param remote the remote
+ * @return a pointer to the url
+ */
+GIT_EXTERN(const char *) git_remote_url(const git_remote *remote);
+
+/**
+ * Get the remote's url for pushing
+ *
+ * If url.*.pushInsteadOf has been configured for this URL, it
+ * will return the modified URL.
+ *
+ * @param remote the remote
+ * @return a pointer to the url or NULL if no special url for pushing is set
+ */
+GIT_EXTERN(const char *) git_remote_pushurl(const git_remote *remote);
+
+/**
+ * Set the remote's url in the configuration
+ *
+ * Remote objects already in memory will not be affected. This assumes
+ * the common case of a single-url remote and will otherwise return an error.
+ *
+ * @param repo the repository in which to perform the change
+ * @param remote the remote's name
+ * @param url the url to set
+ * @return 0 or an error value
+ */
+GIT_EXTERN(int) git_remote_set_url(git_repository *repo, const char *remote, const char* url);
+
+/**
+ * Set the remote's url for pushing in the configuration.
+ *
+ * Remote objects already in memory will not be affected. This assumes
+ * the common case of a single-url remote and will otherwise return an error.
+ *
+ *
+ * @param repo the repository in which to perform the change
+ * @param remote the remote's name
+ * @param url the url to set
+ */
+GIT_EXTERN(int) git_remote_set_pushurl(git_repository *repo, const char *remote, const char* url);
+
+/**
+ * Add a fetch refspec to the remote's configuration
+ *
+ * Add the given refspec to the fetch list in the configuration. No
+ * loaded remote instances will be affected.
+ *
+ * @param repo the repository in which to change the configuration
+ * @param remote the name of the remote to change
+ * @param refspec the new fetch refspec
+ * @return 0, GIT_EINVALIDSPEC if refspec is invalid or an error value
+ */
+GIT_EXTERN(int) git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec);
+
+/**
+ * Get the remote's list of fetch refspecs
+ *
+ * The memory is owned by the user and should be freed with
+ * `git_strarray_free`.
+ *
+ * @param array pointer to the array in which to store the strings
+ * @param remote the remote to query
+ */
+GIT_EXTERN(int) git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote);
+
+/**
+ * Add a push refspec to the remote's configuration
+ *
+ * Add the given refspec to the push list in the configuration. No
+ * loaded remote instances will be affected.
+ *
+ * @param repo the repository in which to change the configuration
+ * @param remote the name of the remote to change
+ * @param refspec the new push refspec
+ * @return 0, GIT_EINVALIDSPEC if refspec is invalid or an error value
+ */
+GIT_EXTERN(int) git_remote_add_push(git_repository *repo, const char *remote, const char *refspec);
+
+/**
+ * Get the remote's list of push refspecs
+ *
+ * The memory is owned by the user and should be freed with
+ * `git_strarray_free`.
+ *
+ * @param array pointer to the array in which to store the strings
+ * @param remote the remote to query
+ */
+GIT_EXTERN(int) git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote);
+
+/**
+ * Get the number of refspecs for a remote
+ *
+ * @param remote the remote
+ * @return the amount of refspecs configured in this remote
+ */
+GIT_EXTERN(size_t) git_remote_refspec_count(const git_remote *remote);
+
+/**
+ * Get a refspec from the remote
+ *
+ * @param remote the remote to query
+ * @param n the refspec to get
+ * @return the nth refspec
+ */
+GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, size_t n);
+
+/**
+ * Open a connection to a remote
+ *
+ * The transport is selected based on the URL. The direction argument
+ * is due to a limitation of the git protocol (over TCP or SSH) which
+ * starts up a specific binary which can only do the one or the other.
+ *
+ * @param remote the remote to connect to
+ * @param direction GIT_DIRECTION_FETCH if you want to fetch or
+ * GIT_DIRECTION_PUSH if you want to push
+ * @param callbacks the callbacks to use for this connection
+ * @param proxy_opts proxy settings
+ * @param custom_headers extra HTTP headers to use in this connection
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy_opts, const git_strarray *custom_headers);
+
+/**
+ * Get the remote repository's reference advertisement list
+ *
+ * Get the list of references with which the server responds to a new
+ * connection.
+ *
+ * The remote (or more exactly its transport) must have connected to
+ * the remote repository. This list is available as soon as the
+ * connection to the remote is initiated and it remains available
+ * after disconnecting.
+ *
+ * The memory belongs to the remote. The pointer will be valid as long
+ * as a new connection is not initiated, but it is recommended that
+ * you make a copy in order to make use of the data.
+ *
+ * @param out pointer to the array
+ * @param size the number of remote heads
+ * @param remote the remote
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote);
+
+/**
+ * Check whether the remote is connected
+ *
+ * Check whether the remote's underlying transport is connected to the
+ * remote host.
+ *
+ * @param remote the remote
+ * @return 1 if it's connected, 0 otherwise.
+ */
+GIT_EXTERN(int) git_remote_connected(const git_remote *remote);
+
+/**
+ * Cancel the operation
+ *
+ * At certain points in its operation, the network code checks whether
+ * the operation has been cancelled and if so stops the operation.
+ *
+ * @param remote the remote
+ */
+GIT_EXTERN(void) git_remote_stop(git_remote *remote);
+
+/**
+ * Disconnect from the remote
+ *
+ * Close the connection to the remote.
+ *
+ * @param remote the remote to disconnect from
+ */
+GIT_EXTERN(void) git_remote_disconnect(git_remote *remote);
+
+/**
+ * Free the memory associated with a remote
+ *
+ * This also disconnects from the remote, if the connection
+ * has not been closed yet (using git_remote_disconnect).
+ *
+ * @param remote the remote to free
+ */
+GIT_EXTERN(void) git_remote_free(git_remote *remote);
+
+/**
+ * Get a list of the configured remotes for a repo
+ *
+ * The string array must be freed by the user.
+ *
+ * @param out a string array which receives the names of the remotes
+ * @param repo the repository to query
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo);
+
+/**
+ * Argument to the completion callback which tells it which operation
+ * finished.
+ */
+typedef enum git_remote_completion_type {
+ GIT_REMOTE_COMPLETION_DOWNLOAD,
+ GIT_REMOTE_COMPLETION_INDEXING,
+ GIT_REMOTE_COMPLETION_ERROR,
+} git_remote_completion_type;
+
+/** Push network progress notification function */
+typedef int (*git_push_transfer_progress)(
+ unsigned int current,
+ unsigned int total,
+ size_t bytes,
+ void* payload);
+/**
+ * Represents an update which will be performed on the remote during push
+ */
+typedef struct {
+ /**
+ * The source name of the reference
+ */
+ char *src_refname;
+ /**
+ * The name of the reference to update on the server
+ */
+ char *dst_refname;
+ /**
+ * The current target of the reference
+ */
+ git_oid src;
+ /**
+ * The new target for the reference
+ */
+ git_oid dst;
+} git_push_update;
+
+/**
+ * Callback used to inform of upcoming updates.
+ *
+ * @param updates an array containing the updates which will be sent
+ * as commands to the destination.
+ * @param len number of elements in `updates`
+ * @param payload Payload provided by the caller
+ */
+typedef int (*git_push_negotiation)(const git_push_update **updates, size_t len, void *payload);
+
+/**
+ * The callback settings structure
+ *
+ * Set the callbacks to be called by the remote when informing the user
+ * about the progress of the network operations.
+ */
+struct git_remote_callbacks {
+ unsigned int version;
+ /**
+ * Textual progress from the remote. Text send over the
+ * progress side-band will be passed to this function (this is
+ * the 'counting objects' output).
+ */
+ git_transport_message_cb sideband_progress;
+
+ /**
+ * Completion is called when different parts of the download
+ * process are done (currently unused).
+ */
+ int (*completion)(git_remote_completion_type type, void *data);
+
+ /**
+ * This will be called if the remote host requires
+ * authentication in order to connect to it.
+ *
+ * Returning GIT_PASSTHROUGH will make libgit2 behave as
+ * though this field isn't set.
+ */
+ git_cred_acquire_cb credentials;
+
+ /**
+ * If cert verification fails, this will be called to let the
+ * user make the final decision of whether to allow the
+ * connection to proceed. Returns 1 to allow the connection, 0
+ * to disallow it or a negative value to indicate an error.
+ */
+ git_transport_certificate_check_cb certificate_check;
+
+ /**
+ * During the download of new data, this will be regularly
+ * called with the current count of progress done by the
+ * indexer.
+ */
+ git_transfer_progress_cb transfer_progress;
+
+ /**
+ * Each time a reference is updated locally, this function
+ * will be called with information about it.
+ */
+ int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
+
+ /**
+ * Function to call with progress information during pack
+ * building. Be aware that this is called inline with pack
+ * building operations, so performance may be affected.
+ */
+ git_packbuilder_progress pack_progress;
+
+ /**
+ * Function to call with progress information during the
+ * upload portion of a push. Be aware that this is called
+ * inline with pack building operations, so performance may be
+ * affected.
+ */
+ git_push_transfer_progress push_transfer_progress;
+
+ /**
+ * Called for each updated reference on push. If `status` is
+ * not `NULL`, the update was rejected by the remote server
+ * and `status` contains the reason given.
+ */
+ int (*push_update_reference)(const char *refname, const char *status, void *data);
+
+ /**
+ * Called once between the negotiation step and the upload. It
+ * provides information about what updates will be performed.
+ */
+ git_push_negotiation push_negotiation;
+
+ /**
+ * Create the transport to use for this operation. Leave NULL
+ * to auto-detect.
+ */
+ git_transport_cb transport;
+
+ /**
+ * This will be passed to each of the callbacks in this struct
+ * as the last parameter.
+ */
+ void *payload;
+};
+
+#define GIT_REMOTE_CALLBACKS_VERSION 1
+#define GIT_REMOTE_CALLBACKS_INIT {GIT_REMOTE_CALLBACKS_VERSION}
+
+/**
+ * Initializes a `git_remote_callbacks` with default values. Equivalent to
+ * creating an instance with GIT_REMOTE_CALLBACKS_INIT.
+ *
+ * @param opts the `git_remote_callbacks` struct to initialize
+ * @param version Version of struct; pass `GIT_REMOTE_CALLBACKS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_remote_init_callbacks(
+ git_remote_callbacks *opts,
+ unsigned int version);
+
+typedef enum {
+ /**
+ * Use the setting from the configuration
+ */
+ GIT_FETCH_PRUNE_UNSPECIFIED,
+ /**
+ * Force pruning on
+ */
+ GIT_FETCH_PRUNE,
+ /**
+ * Force pruning off
+ */
+ GIT_FETCH_NO_PRUNE,
+} git_fetch_prune_t;
+
+/**
+ * Automatic tag following option
+ *
+ * Lets us select the --tags option to use.
+ */
+typedef enum {
+ /**
+ * Use the setting from the configuration.
+ */
+ GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED = 0,
+ /**
+ * Ask the server for tags pointing to objects we're already
+ * downloading.
+ */
+ GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
+ /**
+ * Don't ask for any tags beyond the refspecs.
+ */
+ GIT_REMOTE_DOWNLOAD_TAGS_NONE,
+ /**
+ * Ask for the all the tags.
+ */
+ GIT_REMOTE_DOWNLOAD_TAGS_ALL,
+} git_remote_autotag_option_t;
+
+/**
+ * Fetch options structure.
+ *
+ * Zero out for defaults. Initialize with `GIT_FETCH_OPTIONS_INIT` macro to
+ * correctly set the `version` field. E.g.
+ *
+ * git_fetch_options opts = GIT_FETCH_OPTIONS_INIT;
+ */
+typedef struct {
+ int version;
+
+ /**
+ * Callbacks to use for this fetch operation
+ */
+ git_remote_callbacks callbacks;
+
+ /**
+ * Whether to perform a prune after the fetch
+ */
+ git_fetch_prune_t prune;
+
+ /**
+ * Whether to write the results to FETCH_HEAD. Defaults to
+ * on. Leave this default in order to behave like git.
+ */
+ int update_fetchhead;
+
+ /**
+ * Determines how to behave regarding tags on the remote, such
+ * as auto-downloading tags for objects we're downloading or
+ * downloading all of them.
+ *
+ * The default is to auto-follow tags.
+ */
+ git_remote_autotag_option_t download_tags;
+
+ /**
+ * Proxy options to use, by default no proxy is used.
+ */
+ git_proxy_options proxy_opts;
+
+ /**
+ * Extra headers for this fetch operation
+ */
+ git_strarray custom_headers;
+} git_fetch_options;
+
+#define GIT_FETCH_OPTIONS_VERSION 1
+#define GIT_FETCH_OPTIONS_INIT { GIT_FETCH_OPTIONS_VERSION, GIT_REMOTE_CALLBACKS_INIT, GIT_FETCH_PRUNE_UNSPECIFIED, 1, \
+ GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, GIT_PROXY_OPTIONS_INIT }
+
+/**
+ * Initializes a `git_fetch_options` with default values. Equivalent to
+ * creating an instance with GIT_FETCH_OPTIONS_INIT.
+ *
+ * @param opts the `git_fetch_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_FETCH_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_fetch_init_options(
+ git_fetch_options *opts,
+ unsigned int version);
+
+
+/**
+ * Controls the behavior of a git_push object.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * If the transport being used to push to the remote requires the creation
+ * of a pack file, this controls the number of worker threads used by
+ * the packbuilder when creating that pack file to be sent to the remote.
+ *
+ * If set to 0, the packbuilder will auto-detect the number of threads
+ * to create. The default value is 1.
+ */
+ unsigned int pb_parallelism;
+
+ /**
+ * Callbacks to use for this push operation
+ */
+ git_remote_callbacks callbacks;
+
+ /**
+ * Proxy options to use, by default no proxy is used.
+ */
+ git_proxy_options proxy_opts;
+
+ /**
+ * Extra headers for this push operation
+ */
+ git_strarray custom_headers;
+} git_push_options;
+
+#define GIT_PUSH_OPTIONS_VERSION 1
+#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION, 0, GIT_REMOTE_CALLBACKS_INIT, GIT_PROXY_OPTIONS_INIT }
+
+/**
+ * Initializes a `git_push_options` with default values. Equivalent to
+ * creating an instance with GIT_PUSH_OPTIONS_INIT.
+ *
+ * @param opts the `git_push_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_PUSH_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_push_init_options(
+ git_push_options *opts,
+ unsigned int version);
+
+/**
+ * Download and index the packfile
+ *
+ * Connect to the remote if it hasn't been done yet, negotiate with
+ * the remote git which objects are missing, download and index the
+ * packfile.
+ *
+ * The .idx file will be created and both it and the packfile with be
+ * renamed to their final name.
+ *
+ * @param remote the remote
+ * @param refspecs the refspecs to use for this negotiation and
+ * download. Use NULL or an empty array to use the base refspecs
+ * @param opts the options to use for this fetch
+ * @return 0 or an error code
+ */
+ GIT_EXTERN(int) git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_fetch_options *opts);
+
+/**
+ * Create a packfile and send it to the server
+ *
+ * Connect to the remote if it hasn't been done yet, negotiate with
+ * the remote git which objects are missing, create a packfile with the missing objects and send it.
+ *
+ * @param remote the remote
+ * @param refspecs the refspecs to use for this negotiation and
+ * upload. Use NULL or an empty array to use the base refspecs
+ * @param opts the options to use for this push
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_upload(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts);
+
+/**
+ * Update the tips to the new state
+ *
+ * @param remote the remote to update
+ * @param reflog_message The message to insert into the reflogs. If
+ * NULL and fetching, the default is "fetch <name>", where <name> is
+ * the name of the remote (or its url, for in-memory remotes). This
+ * parameter is ignored when pushing.
+ * @param callbacks pointer to the callback structure to use
+ * @param update_fetchhead whether to write to FETCH_HEAD. Pass 1 to behave like git.
+ * @param download_tags what the behaviour for downloading tags is for this fetch. This is
+ * ignored for push. This must be the same value passed to `git_remote_download()`.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_update_tips(
+ git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ int update_fetchhead,
+ git_remote_autotag_option_t download_tags,
+ const char *reflog_message);
+
+/**
+ * Download new data and update tips
+ *
+ * Convenience function to connect to a remote, download the data,
+ * disconnect and update the remote-tracking branches.
+ *
+ * @param remote the remote to fetch from
+ * @param refspecs the refspecs to use for this fetch. Pass NULL or an
+ * empty array to use the base refspecs.
+ * @param opts options to use for this fetch
+ * @param reflog_message The message to insert into the reflogs. If NULL, the
+ * default is "fetch"
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_fetch(
+ git_remote *remote,
+ const git_strarray *refspecs,
+ const git_fetch_options *opts,
+ const char *reflog_message);
+
+/**
+ * Prune tracking refs that are no longer present on remote
+ *
+ * @param remote the remote to prune
+ * @param callbacks callbacks to use for this prune
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks);
+
+/**
+ * Perform a push
+ *
+ * Peform all the steps from a push.
+ *
+ * @param remote the remote to push to
+ * @param refspecs the refspecs to use for pushing. If none are
+ * passed, the configured refspecs will be used
+ * @param opts options to use for this push
+ */
+GIT_EXTERN(int) git_remote_push(git_remote *remote,
+ const git_strarray *refspecs,
+ const git_push_options *opts);
+
+/**
+ * Get the statistics structure that is filled in by the fetch operation.
+ */
+GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote);
+
+/**
+ * Retrieve the tag auto-follow setting
+ *
+ * @param remote the remote to query
+ * @return the auto-follow setting
+ */
+GIT_EXTERN(git_remote_autotag_option_t) git_remote_autotag(const git_remote *remote);
+
+/**
+ * Set the remote's tag following setting.
+ *
+ * The change will be made in the configuration. No loaded remotes
+ * will be affected.
+ *
+ * @param repo the repository in which to make the change
+ * @param remote the name of the remote
+ * @param value the new value to take.
+ */
+GIT_EXTERN(int) git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value);
+/**
+ * Retrieve the ref-prune setting
+ *
+ * @param remote the remote to query
+ * @return the ref-prune setting
+ */
+GIT_EXTERN(int) git_remote_prune_refs(const git_remote *remote);
+
+/**
+ * Give the remote a new name
+ *
+ * All remote-tracking branches and configuration settings
+ * for the remote are updated.
+ *
+ * The new name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * No loaded instances of a the remote with the old name will change
+ * their name or their list of refspecs.
+ *
+ * @param problems non-default refspecs cannot be renamed and will be
+ * stored here for further processing by the caller. Always free this
+ * strarray on successful return.
+ * @param repo the repository in which to rename
+ * @param name the current name of the remote
+ * @param new_name the new name the remote should bear
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+GIT_EXTERN(int) git_remote_rename(
+ git_strarray *problems,
+ git_repository *repo,
+ const char *name,
+ const char *new_name);
+
+/**
+ * Ensure the remote name is well-formed.
+ *
+ * @param remote_name name to be checked.
+ * @return 1 if the reference name is acceptable; 0 if it isn't
+ */
+GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name);
+
+/**
+* Delete an existing persisted remote.
+*
+* All remote-tracking branches and configuration settings
+* for the remote will be removed.
+*
+* @param repo the repository in which to act
+* @param name the name of the remove to delete
+* @return 0 on success, or an error code.
+*/
+GIT_EXTERN(int) git_remote_delete(git_repository *repo, const char *name);
+
+/**
+ * Retrieve the name of the remote's default branch
+ *
+ * The default branch of a repository is the branch which HEAD points
+ * to. If the remote does not support reporting this information
+ * directly, it performs the guess as git does; that is, if there are
+ * multiple branches which point to the same commit, the first one is
+ * chosen. If the master branch is a candidate, it wins.
+ *
+ * This function must only be called after connecting.
+ *
+ * @param out the buffern in which to store the reference name
+ * @param remote the remote
+ * @return 0, GIT_ENOTFOUND if the remote does not have any references
+ * or none of them point to HEAD's commit, or an error message.
+ */
+GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_repository_h__
+#define INCLUDE_git_repository_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "buffer.h"
+
+/**
+ * @file git2/repository.h
+ * @brief Git repository management routines
+ * @defgroup git_repository Git repository management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Open a git repository.
+ *
+ * The 'path' argument must point to either a git repository
+ * folder, or an existing work dir.
+ *
+ * The method will automatically detect if 'path' is a normal
+ * or bare repository or fail is 'path' is neither.
+ *
+ * @param out pointer to the repo which will be opened
+ * @param path the path to the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path);
+
+/**
+ * Create a "fake" repository to wrap an object database
+ *
+ * Create a repository object to wrap an object database to be used
+ * with the API when all you have is an object database. This doesn't
+ * have any paths associated with it, so use with care.
+ *
+ * @param out pointer to the repo
+ * @param odb the object database to wrap
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_repository_wrap_odb(git_repository **out, git_odb *odb);
+
+/**
+ * Look for a git repository and copy its path in the given buffer.
+ * The lookup start from base_path and walk across parent directories
+ * if nothing has been found. The lookup ends when the first repository
+ * is found, or when reaching a directory referenced in ceiling_dirs
+ * or when the filesystem changes (in case across_fs is true).
+ *
+ * The method will automatically detect if the repository is bare
+ * (if there is a repository).
+ *
+ * @param out A pointer to a user-allocated git_buf which will contain
+ * the found path.
+ *
+ * @param start_path The base path where the lookup starts.
+ *
+ * @param across_fs If true, then the lookup will not stop when a
+ * filesystem device change is detected while exploring parent directories.
+ *
+ * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR separated list of
+ * absolute symbolic link free paths. The lookup will stop when any
+ * of this paths is reached. Note that the lookup always performs on
+ * start_path no matter start_path appears in ceiling_dirs ceiling_dirs
+ * might be NULL (which is equivalent to an empty string)
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_repository_discover(
+ git_buf *out,
+ const char *start_path,
+ int across_fs,
+ const char *ceiling_dirs);
+
+/**
+ * Option flags for `git_repository_open_ext`.
+ *
+ * * GIT_REPOSITORY_OPEN_NO_SEARCH - Only open the repository if it can be
+ * immediately found in the start_path. Do not walk up from the
+ * start_path looking at parent directories.
+ * * GIT_REPOSITORY_OPEN_CROSS_FS - Unless this flag is set, open will not
+ * continue searching across filesystem boundaries (i.e. when `st_dev`
+ * changes from the `stat` system call). (E.g. Searching in a user's home
+ * directory "/home/user/source/" will not return "/.git/" as the found
+ * repo if "/" is a different filesystem than "/home".)
+ * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless
+ * of core.bare config, and defer loading config file for faster setup.
+ * Unlike `git_repository_open_bare`, this can follow gitlinks.
+ * * GIT_REPOSITORY_OPEN_NO_DOTGIT - Do not check for a repository by
+ * appending /.git to the start_path; only open the repository if
+ * start_path itself points to the git directory.
+ * * GIT_REPOSITORY_OPEN_FROM_ENV - Find and open a git repository,
+ * respecting the environment variables used by the git command-line
+ * tools. If set, `git_repository_open_ext` will ignore the other
+ * flags and the `ceiling_dirs` argument, and will allow a NULL `path`
+ * to use `GIT_DIR` or search from the current directory. The search
+ * for a repository will respect $GIT_CEILING_DIRECTORIES and
+ * $GIT_DISCOVERY_ACROSS_FILESYSTEM. The opened repository will
+ * respect $GIT_INDEX_FILE, $GIT_NAMESPACE, $GIT_OBJECT_DIRECTORY, and
+ * $GIT_ALTERNATE_OBJECT_DIRECTORIES. In the future, this flag will
+ * also cause `git_repository_open_ext` to respect $GIT_WORK_TREE and
+ * $GIT_COMMON_DIR; currently, `git_repository_open_ext` with this
+ * flag will error out if either $GIT_WORK_TREE or $GIT_COMMON_DIR is
+ * set.
+ */
+typedef enum {
+ GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
+ GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),
+ GIT_REPOSITORY_OPEN_BARE = (1 << 2),
+ GIT_REPOSITORY_OPEN_NO_DOTGIT = (1 << 3),
+ GIT_REPOSITORY_OPEN_FROM_ENV = (1 << 4),
+} git_repository_open_flag_t;
+
+/**
+ * Find and open a repository with extended controls.
+ *
+ * @param out Pointer to the repo which will be opened. This can
+ * actually be NULL if you only want to use the error code to
+ * see if a repo at this path could be opened.
+ * @param path Path to open as git repository. If the flags
+ * permit "searching", then this can be a path to a subdirectory
+ * inside the working directory of the repository. May be NULL if
+ * flags is GIT_REPOSITORY_OPEN_FROM_ENV.
+ * @param flags A combination of the GIT_REPOSITORY_OPEN flags above.
+ * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR delimited list of path
+ * prefixes at which the search for a containing repository should
+ * terminate.
+ * @return 0 on success, GIT_ENOTFOUND if no repository could be found,
+ * or -1 if there was a repository but open failed for some reason
+ * (such as repo corruption or system errors).
+ */
+GIT_EXTERN(int) git_repository_open_ext(
+ git_repository **out,
+ const char *path,
+ unsigned int flags,
+ const char *ceiling_dirs);
+
+/**
+ * Open a bare repository on the serverside.
+ *
+ * This is a fast open for bare repositories that will come in handy
+ * if you're e.g. hosting git repositories and need to access them
+ * efficiently
+ *
+ * @param out Pointer to the repo which will be opened.
+ * @param bare_path Direct path to the bare repository
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_open_bare(git_repository **out, const char *bare_path);
+
+/**
+ * Free a previously allocated repository
+ *
+ * Note that after a repository is free'd, all the objects it has spawned
+ * will still exist until they are manually closed by the user
+ * with `git_object_free`, but accessing any of the attributes of
+ * an object without a backing repository will result in undefined
+ * behavior
+ *
+ * @param repo repository handle to close. If NULL nothing occurs.
+ */
+GIT_EXTERN(void) git_repository_free(git_repository *repo);
+
+/**
+ * Creates a new Git repository in the given folder.
+ *
+ * TODO:
+ * - Reinit the repository
+ *
+ * @param out pointer to the repo which will be created or reinitialized
+ * @param path the path to the repository
+ * @param is_bare if true, a Git repository without a working directory is
+ * created at the pointed path. If false, provided path will be
+ * considered as the working directory into which the .git directory
+ * will be created.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_repository_init(
+ git_repository **out,
+ const char *path,
+ unsigned is_bare);
+
+/**
+ * Option flags for `git_repository_init_ext`.
+ *
+ * These flags configure extra behaviors to `git_repository_init_ext`.
+ * In every case, the default behavior is the zero value (i.e. flag is
+ * not set). Just OR the flag values together for the `flags` parameter
+ * when initializing a new repo. Details of individual values are:
+ *
+ * * BARE - Create a bare repository with no working directory.
+ * * NO_REINIT - Return an GIT_EEXISTS error if the repo_path appears to
+ * already be an git repository.
+ * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo
+ * path for non-bare repos (if it is not already there), but
+ * passing this flag prevents that behavior.
+ * * MKDIR - Make the repo_path (and workdir_path) as needed. Init is
+ * always willing to create the ".git" directory even without this
+ * flag. This flag tells init to create the trailing component of
+ * the repo and workdir paths as needed.
+ * * MKPATH - Recursively make all components of the repo and workdir
+ * paths as necessary.
+ * * EXTERNAL_TEMPLATE - libgit2 normally uses internal templates to
+ * initialize a new repo. This flags enables external templates,
+ * looking the "template_path" from the options if set, or the
+ * `init.templatedir` global config if not, or falling back on
+ * "/usr/share/git-core/templates" if it exists.
+ * * GIT_REPOSITORY_INIT_RELATIVE_GITLINK - If an alternate workdir is
+ * specified, use relative paths for the gitdir and core.worktree.
+ */
+typedef enum {
+ GIT_REPOSITORY_INIT_BARE = (1u << 0),
+ GIT_REPOSITORY_INIT_NO_REINIT = (1u << 1),
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = (1u << 2),
+ GIT_REPOSITORY_INIT_MKDIR = (1u << 3),
+ GIT_REPOSITORY_INIT_MKPATH = (1u << 4),
+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5),
+ GIT_REPOSITORY_INIT_RELATIVE_GITLINK = (1u << 6),
+} git_repository_init_flag_t;
+
+/**
+ * Mode options for `git_repository_init_ext`.
+ *
+ * Set the mode field of the `git_repository_init_options` structure
+ * either to the custom mode that you would like, or to one of the
+ * following modes:
+ *
+ * * SHARED_UMASK - Use permissions configured by umask - the default.
+ * * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo
+ * to be group writable and "g+sx" for sticky group assignment.
+ * * SHARED_ALL - Use "--shared=all" behavior, adding world readability.
+ * * Anything else - Set to custom value.
+ */
+typedef enum {
+ GIT_REPOSITORY_INIT_SHARED_UMASK = 0,
+ GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775,
+ GIT_REPOSITORY_INIT_SHARED_ALL = 0002777,
+} git_repository_init_mode_t;
+
+/**
+ * Extended options structure for `git_repository_init_ext`.
+ *
+ * This contains extra options for `git_repository_init_ext` that enable
+ * additional initialization features. The fields are:
+ *
+ * * flags - Combination of GIT_REPOSITORY_INIT flags above.
+ * * mode - Set to one of the standard GIT_REPOSITORY_INIT_SHARED_...
+ * constants above, or to a custom value that you would like.
+ * * workdir_path - The path to the working dir or NULL for default (i.e.
+ * repo_path parent on non-bare repos). IF THIS IS RELATIVE PATH,
+ * IT WILL BE EVALUATED RELATIVE TO THE REPO_PATH. If this is not
+ * the "natural" working directory, a .git gitlink file will be
+ * created here linking to the repo_path.
+ * * description - If set, this will be used to initialize the "description"
+ * file in the repository, instead of using the template content.
+ * * template_path - When GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE is set,
+ * this contains the path to use for the template directory. If
+ * this is NULL, the config or default directory options will be
+ * used instead.
+ * * initial_head - The name of the head to point HEAD at. If NULL, then
+ * this will be treated as "master" and the HEAD ref will be set
+ * to "refs/heads/master". If this begins with "refs/" it will be
+ * used verbatim; otherwise "refs/heads/" will be prefixed.
+ * * origin_url - If this is non-NULL, then after the rest of the
+ * repository initialization is completed, an "origin" remote
+ * will be added pointing to this URL.
+ */
+typedef struct {
+ unsigned int version;
+ uint32_t flags;
+ uint32_t mode;
+ const char *workdir_path;
+ const char *description;
+ const char *template_path;
+ const char *initial_head;
+ const char *origin_url;
+} git_repository_init_options;
+
+#define GIT_REPOSITORY_INIT_OPTIONS_VERSION 1
+#define GIT_REPOSITORY_INIT_OPTIONS_INIT {GIT_REPOSITORY_INIT_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_repository_init_options` with default values. Equivalent
+ * to creating an instance with GIT_REPOSITORY_INIT_OPTIONS_INIT.
+ *
+ * @param opts the `git_repository_init_options` struct to initialize
+ * @param version Version of struct; pass `GIT_REPOSITORY_INIT_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_repository_init_init_options(
+ git_repository_init_options *opts,
+ unsigned int version);
+
+/**
+ * Create a new Git repository in the given folder with extended controls.
+ *
+ * This will initialize a new git repository (creating the repo_path
+ * if requested by flags) and working directory as needed. It will
+ * auto-detect the case sensitivity of the file system and if the
+ * file system supports file mode bits correctly.
+ *
+ * @param out Pointer to the repo which will be created or reinitialized.
+ * @param repo_path The path to the repository.
+ * @param opts Pointer to git_repository_init_options struct.
+ * @return 0 or an error code on failure.
+ */
+GIT_EXTERN(int) git_repository_init_ext(
+ git_repository **out,
+ const char *repo_path,
+ git_repository_init_options *opts);
+
+/**
+ * Retrieve and resolve the reference pointed at by HEAD.
+ *
+ * The returned `git_reference` will be owned by caller and
+ * `git_reference_free()` must be called when done with it to release the
+ * allocated memory and prevent a leak.
+ *
+ * @param out pointer to the reference which will be retrieved
+ * @param repo a repository object
+ *
+ * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
+ * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise
+ */
+GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
+
+/**
+ * Check if a repository's HEAD is detached
+ *
+ * A repository's HEAD is detached when it points directly to a commit
+ * instead of a branch.
+ *
+ * @param repo Repo to test
+ * @return 1 if HEAD is detached, 0 if it's not; error code if there
+ * was an error.
+ */
+GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
+
+/**
+ * Check if the current branch is unborn
+ *
+ * An unborn branch is one named from HEAD but which doesn't exist in
+ * the refs namespace, because it doesn't have any commit to point to.
+ *
+ * @param repo Repo to test
+ * @return 1 if the current branch is unborn, 0 if it's not; error
+ * code if there was an error
+ */
+GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo);
+
+/**
+ * Check if a repository is empty
+ *
+ * An empty repository has just been initialized and contains no references
+ * apart from HEAD, which must be pointing to the unborn master branch.
+ *
+ * @param repo Repo to test
+ * @return 1 if the repository is empty, 0 if it isn't, error code
+ * if the repository is corrupted
+ */
+GIT_EXTERN(int) git_repository_is_empty(git_repository *repo);
+
+/**
+ * Get the path of this repository
+ *
+ * This is the path of the `.git` folder for normal repositories,
+ * or of the repository itself for bare repositories.
+ *
+ * @param repo A repository object
+ * @return the path to the repository
+ */
+GIT_EXTERN(const char *) git_repository_path(git_repository *repo);
+
+/**
+ * Get the path of the working directory for this repository
+ *
+ * If the repository is bare, this function will always return
+ * NULL.
+ *
+ * @param repo A repository object
+ * @return the path to the working dir, if it exists
+ */
+GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo);
+
+/**
+ * Set the path to the working directory for this repository
+ *
+ * The working directory doesn't need to be the same one
+ * that contains the `.git` folder for this repository.
+ *
+ * If this repository is bare, setting its working directory
+ * will turn it into a normal repository, capable of performing
+ * all the common workdir operations (checkout, status, index
+ * manipulation, etc).
+ *
+ * @param repo A repository object
+ * @param workdir The path to a working directory
+ * @param update_gitlink Create/update gitlink in workdir and set config
+ * "core.worktree" (if workdir is not the parent of the .git directory)
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_workdir(
+ git_repository *repo, const char *workdir, int update_gitlink);
+
+/**
+ * Check if a repository is bare
+ *
+ * @param repo Repo to test
+ * @return 1 if the repository is bare, 0 otherwise.
+ */
+GIT_EXTERN(int) git_repository_is_bare(git_repository *repo);
+
+/**
+ * Get the configuration file for this repository.
+ *
+ * If a configuration file has not been set, the default
+ * config set for the repository will be returned, including
+ * global and system configurations (if they are available).
+ *
+ * The configuration file must be freed once it's no longer
+ * being used by the user.
+ *
+ * @param out Pointer to store the loaded configuration
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_config(git_config **out, git_repository *repo);
+
+/**
+ * Get a snapshot of the repository's configuration
+ *
+ * Convenience function to take a snapshot from the repository's
+ * configuration. The contents of this snapshot will not change,
+ * even if the underlying config files are modified.
+ *
+ * The configuration file must be freed once it's no longer
+ * being used by the user.
+ *
+ * @param out Pointer to store the loaded configuration
+ * @param repo the repository
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_config_snapshot(git_config **out, git_repository *repo);
+
+/**
+ * Get the Object Database for this repository.
+ *
+ * If a custom ODB has not been set, the default
+ * database for the repository will be returned (the one
+ * located in `.git/objects`).
+ *
+ * The ODB must be freed once it's no longer being used by
+ * the user.
+ *
+ * @param out Pointer to store the loaded ODB
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_odb(git_odb **out, git_repository *repo);
+
+/**
+ * Get the Reference Database Backend for this repository.
+ *
+ * If a custom refsdb has not been set, the default database for
+ * the repository will be returned (the one that manipulates loose
+ * and packed references in the `.git` directory).
+ *
+ * The refdb must be freed once it's no longer being used by
+ * the user.
+ *
+ * @param out Pointer to store the loaded refdb
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_refdb(git_refdb **out, git_repository *repo);
+
+/**
+ * Get the Index file for this repository.
+ *
+ * If a custom index has not been set, the default
+ * index for the repository will be returned (the one
+ * located in `.git/index`).
+ *
+ * The index must be freed once it's no longer being used by
+ * the user.
+ *
+ * @param out Pointer to store the loaded index
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo);
+
+/**
+ * Retrieve git's prepared message
+ *
+ * Operations such as git revert/cherry-pick/merge with the -n option
+ * stop just short of creating a commit with the changes and save
+ * their prepared message in .git/MERGE_MSG so the next git-commit
+ * execution can present it to the user for them to amend if they
+ * wish.
+ *
+ * Use this function to get the contents of this file. Don't forget to
+ * remove the file after you create the commit.
+ *
+ * @param out git_buf to write data into
+ * @param repo Repository to read prepared message from
+ * @return 0, GIT_ENOTFOUND if no message exists or an error code
+ */
+GIT_EXTERN(int) git_repository_message(git_buf *out, git_repository *repo);
+
+/**
+ * Remove git's prepared message.
+ *
+ * Remove the message that `git_repository_message` retrieves.
+ */
+GIT_EXTERN(int) git_repository_message_remove(git_repository *repo);
+
+/**
+ * Remove all the metadata associated with an ongoing command like merge,
+ * revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc.
+ *
+ * @param repo A repository object
+ * @return 0 on success, or error
+ */
+GIT_EXTERN(int) git_repository_state_cleanup(git_repository *repo);
+
+typedef int (*git_repository_fetchhead_foreach_cb)(const char *ref_name,
+ const char *remote_url,
+ const git_oid *oid,
+ unsigned int is_merge,
+ void *payload);
+
+/**
+ * Invoke 'callback' for each entry in the given FETCH_HEAD file.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param repo A repository object
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ * @return 0 on success, non-zero callback return value, GIT_ENOTFOUND if
+ * there is no FETCH_HEAD file, or other error code.
+ */
+GIT_EXTERN(int) git_repository_fetchhead_foreach(
+ git_repository *repo,
+ git_repository_fetchhead_foreach_cb callback,
+ void *payload);
+
+typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid,
+ void *payload);
+
+/**
+ * If a merge is in progress, invoke 'callback' for each commit ID in the
+ * MERGE_HEAD file.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param repo A repository object
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ * @return 0 on success, non-zero callback return value, GIT_ENOTFOUND if
+ * there is no MERGE_HEAD file, or other error code.
+ */
+GIT_EXTERN(int) git_repository_mergehead_foreach(
+ git_repository *repo,
+ git_repository_mergehead_foreach_cb callback,
+ void *payload);
+
+/**
+ * Calculate hash of file using repository filtering rules.
+ *
+ * If you simply want to calculate the hash of a file on disk with no filters,
+ * you can just use the `git_odb_hashfile()` API. However, if you want to
+ * hash a file in the repository and you want to apply filtering rules (e.g.
+ * crlf filters) before generating the SHA, then use this function.
+ *
+ * Note: if the repository has `core.safecrlf` set to fail and the
+ * filtering triggers that failure, then this function will return an
+ * error and not calculate the hash of the file.
+ *
+ * @param out Output value of calculated SHA
+ * @param repo Repository pointer
+ * @param path Path to file on disk whose contents should be hashed. If the
+ * repository is not NULL, this can be a relative path.
+ * @param type The object type to hash as (e.g. GIT_OBJ_BLOB)
+ * @param as_path The path to use to look up filtering rules. If this is
+ * NULL, then the `path` parameter will be used instead. If
+ * this is passed as the empty string, then no filters will be
+ * applied when calculating the hash.
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_hashfile(
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path);
+
+/**
+ * Make the repository HEAD point to the specified reference.
+ *
+ * If the provided reference points to a Tree or a Blob, the HEAD is
+ * unaltered and -1 is returned.
+ *
+ * If the provided reference points to a branch, the HEAD will point
+ * to that branch, staying attached, or become attached if it isn't yet.
+ * If the branch doesn't exist yet, no error will be return. The HEAD
+ * will then be attached to an unborn branch.
+ *
+ * Otherwise, the HEAD will be detached and will directly point to
+ * the Commit.
+ *
+ * @param repo Repository pointer
+ * @param refname Canonical name of the reference the HEAD should point at
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_head(
+ git_repository* repo,
+ const char* refname);
+
+/**
+ * Make the repository HEAD directly point to the Commit.
+ *
+ * If the provided committish cannot be found in the repository, the HEAD
+ * is unaltered and GIT_ENOTFOUND is returned.
+ *
+ * If the provided commitish cannot be peeled into a commit, the HEAD
+ * is unaltered and -1 is returned.
+ *
+ * Otherwise, the HEAD will eventually be detached and will directly point to
+ * the peeled Commit.
+ *
+ * @param repo Repository pointer
+ * @param commitish Object id of the Commit the HEAD should point to
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_head_detached(
+ git_repository* repo,
+ const git_oid* commitish);
+
+/**
+ * Make the repository HEAD directly point to the Commit.
+ *
+ * This behaves like `git_repository_set_head_detached()` but takes an
+ * annotated commit, which lets you specify which extended sha syntax
+ * string was specified by a user, allowing for more exact reflog
+ * messages.
+ *
+ * See the documentation for `git_repository_set_head_detached()`.
+ *
+ * @see git_repository_set_head_detached
+ */
+GIT_EXTERN(int) git_repository_set_head_detached_from_annotated(
+ git_repository *repo,
+ const git_annotated_commit *commitish);
+
+/**
+ * Detach the HEAD.
+ *
+ * If the HEAD is already detached and points to a Commit, 0 is returned.
+ *
+ * If the HEAD is already detached and points to a Tag, the HEAD is
+ * updated into making it point to the peeled Commit, and 0 is returned.
+ *
+ * If the HEAD is already detached and points to a non commitish, the HEAD is
+ * unaltered, and -1 is returned.
+ *
+ * Otherwise, the HEAD will be detached and point to the peeled Commit.
+ *
+ * @param repo Repository pointer
+ * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
+ * branch or an error code
+ */
+GIT_EXTERN(int) git_repository_detach_head(
+ git_repository* repo);
+
+/**
+ * Repository state
+ *
+ * These values represent possible states for the repository to be in,
+ * based on the current operation which is ongoing.
+ */
+typedef enum {
+ GIT_REPOSITORY_STATE_NONE,
+ GIT_REPOSITORY_STATE_MERGE,
+ GIT_REPOSITORY_STATE_REVERT,
+ GIT_REPOSITORY_STATE_REVERT_SEQUENCE,
+ GIT_REPOSITORY_STATE_CHERRYPICK,
+ GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE,
+ GIT_REPOSITORY_STATE_BISECT,
+ GIT_REPOSITORY_STATE_REBASE,
+ GIT_REPOSITORY_STATE_REBASE_INTERACTIVE,
+ GIT_REPOSITORY_STATE_REBASE_MERGE,
+ GIT_REPOSITORY_STATE_APPLY_MAILBOX,
+ GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE,
+} git_repository_state_t;
+
+/**
+ * Determines the status of a git repository - ie, whether an operation
+ * (merge, cherry-pick, etc) is in progress.
+ *
+ * @param repo Repository pointer
+ * @return The state of the repository
+ */
+GIT_EXTERN(int) git_repository_state(git_repository *repo);
+
+/**
+ * Sets the active namespace for this Git Repository
+ *
+ * This namespace affects all reference operations for the repo.
+ * See `man gitnamespaces`
+ *
+ * @param repo The repo
+ * @param nmspace The namespace. This should not include the refs
+ * folder, e.g. to namespace all references under `refs/namespaces/foo/`,
+ * use `foo` as the namespace.
+ * @return 0 on success, -1 on error
+ */
+GIT_EXTERN(int) git_repository_set_namespace(git_repository *repo, const char *nmspace);
+
+/**
+ * Get the currently active namespace for this repository
+ *
+ * @param repo The repo
+ * @return the active namespace, or NULL if there isn't one
+ */
+GIT_EXTERN(const char *) git_repository_get_namespace(git_repository *repo);
+
+
+/**
+ * Determine if the repository was a shallow clone
+ *
+ * @param repo The repository
+ * @return 1 if shallow, zero if not
+ */
+GIT_EXTERN(int) git_repository_is_shallow(git_repository *repo);
+
+/**
+ * Retrieve the configured identity to use for reflogs
+ *
+ * The memory is owned by the repository and must not be freed by the
+ * user.
+ *
+ * @param name where to store the pointer to the name
+ * @param email where to store the pointer to the email
+ * @param repo the repository
+ */
+GIT_EXTERN(int) git_repository_ident(const char **name, const char **email, const git_repository *repo);
+
+/**
+ * Set the identity to be used for writing reflogs
+ *
+ * If both are set, this name and email will be used to write to the
+ * reflog. Pass NULL to unset. When unset, the identity will be taken
+ * from the repository's configuration.
+ *
+ * @param repo the repository to configure
+ * @param name the name to use for the reflog entries
+ * @param email the email to use for the reflog entries
+ */
+GIT_EXTERN(int) git_repository_set_ident(git_repository *repo, const char *name, const char *email);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_reset_h__
+#define INCLUDE_git_reset_h__
+
+#include "common.h"
+#include "types.h"
+#include "strarray.h"
+#include "checkout.h"
+
+/**
+ * @file git2/reset.h
+ * @brief Git reset management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Kinds of reset operation
+ */
+typedef enum {
+ GIT_RESET_SOFT = 1, /**< Move the head to the given commit */
+ GIT_RESET_MIXED = 2, /**< SOFT plus reset index to the commit */
+ GIT_RESET_HARD = 3, /**< MIXED plus changes in working tree discarded */
+} git_reset_t;
+
+/**
+ * Sets the current head to the specified commit oid and optionally
+ * resets the index and working tree to match.
+ *
+ * SOFT reset means the Head will be moved to the commit.
+ *
+ * MIXED reset will trigger a SOFT reset, plus the index will be replaced
+ * with the content of the commit tree.
+ *
+ * HARD reset will trigger a MIXED reset and the working directory will be
+ * replaced with the content of the index. (Untracked and ignored files
+ * will be left alone, however.)
+ *
+ * TODO: Implement remaining kinds of resets.
+ *
+ * @param repo Repository where to perform the reset operation.
+ *
+ * @param target Committish to which the Head should be moved to. This object
+ * must belong to the given `repo` and can either be a git_commit or a
+ * git_tag. When a git_tag is being passed, it should be dereferencable
+ * to a git_commit which oid will be used as the target of the branch.
+ *
+ * @param reset_type Kind of reset operation to perform.
+ *
+ * @param checkout_opts Checkout options to be used for a HARD reset.
+ * The checkout_strategy field will be overridden (based on reset_type).
+ * This parameter can be used to propagate notify and progress callbacks.
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_reset(
+ git_repository *repo,
+ git_object *target,
+ git_reset_t reset_type,
+ const git_checkout_options *checkout_opts);
+
+/**
+ * Sets the current head to the specified commit oid and optionally
+ * resets the index and working tree to match.
+ *
+ * This behaves like `git_reset()` but takes an annotated commit,
+ * which lets you specify which extended sha syntax string was
+ * specified by a user, allowing for more exact reflog messages.
+ *
+ * See the documentation for `git_reset()`.
+ *
+ * @see git_reset
+ */
+GIT_EXTERN(int) git_reset_from_annotated(
+ git_repository *repo,
+ git_annotated_commit *commit,
+ git_reset_t reset_type,
+ const git_checkout_options *checkout_opts);
+
+/**
+ * Updates some entries in the index from the target commit tree.
+ *
+ * The scope of the updated entries is determined by the paths
+ * being passed in the `pathspec` parameters.
+ *
+ * Passing a NULL `target` will result in removing
+ * entries in the index matching the provided pathspecs.
+ *
+ * @param repo Repository where to perform the reset operation.
+ *
+ * @param target The committish which content will be used to reset the content
+ * of the index.
+ *
+ * @param pathspecs List of pathspecs to operate on.
+ *
+ * @return 0 on success or an error code < 0
+ */
+GIT_EXTERN(int) git_reset_default(
+ git_repository *repo,
+ git_object *target,
+ git_strarray* pathspecs);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_revert_h__
+#define INCLUDE_git_revert_h__
+
+#include "common.h"
+#include "types.h"
+#include "merge.h"
+
+/**
+ * @file git2/revert.h
+ * @brief Git revert routines
+ * @defgroup git_revert Git revert routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Options for revert
+ */
+typedef struct {
+ unsigned int version;
+
+ /** For merge commits, the "mainline" is treated as the parent. */
+ unsigned int mainline;
+
+ git_merge_options merge_opts; /**< Options for the merging */
+ git_checkout_options checkout_opts; /**< Options for the checkout */
+} git_revert_options;
+
+#define GIT_REVERT_OPTIONS_VERSION 1
+#define GIT_REVERT_OPTIONS_INIT {GIT_REVERT_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT}
+
+/**
+ * Initializes a `git_revert_options` with default values. Equivalent to
+ * creating an instance with GIT_REVERT_OPTIONS_INIT.
+ *
+ * @param opts the `git_revert_options` struct to initialize
+ * @param version Version of struct; pass `GIT_REVERT_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_revert_init_options(
+ git_revert_options *opts,
+ unsigned int version);
+
+/**
+ * Reverts the given commit against the given "our" commit, producing an
+ * index that reflects the result of the revert.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo the repository that contains the given commits
+ * @param revert_commit the commit to revert
+ * @param our_commit the commit to revert against (eg, HEAD)
+ * @param mainline the parent of the revert commit, if it is a merge
+ * @param merge_options the merge options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_revert_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *revert_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_options);
+
+/**
+ * Reverts the given commit, producing changes in the index and working directory.
+ *
+ * @param repo the repository to revert
+ * @param commit the commit to revert
+ * @param given_opts merge flags
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_revert(
+ git_repository *repo,
+ git_commit *commit,
+ const git_revert_options *given_opts);
+
+/** @} */
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_revparse_h__
+#define INCLUDE_git_revparse_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/revparse.h
+ * @brief Git revision parsing routines
+ * @defgroup git_revparse Git revision parsing routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Find a single object, as specified by a revision string.
+ *
+ * See `man gitrevisions`, or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * The returned object should be released with `git_object_free` when no
+ * longer needed.
+ *
+ * @param out pointer to output object
+ * @param repo the repository to search in
+ * @param spec the textual specification for an object
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_revparse_single(
+ git_object **out, git_repository *repo, const char *spec);
+
+/**
+ * Find a single object and intermediate reference by a revision string.
+ *
+ * See `man gitrevisions`, or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * In some cases (`@{<-n>}` or `<branchname>@{upstream}`), the expression may
+ * point to an intermediate reference. When such expressions are being passed
+ * in, `reference_out` will be valued as well.
+ *
+ * The returned object should be released with `git_object_free` and the
+ * returned reference with `git_reference_free` when no longer needed.
+ *
+ * @param object_out pointer to output object
+ * @param reference_out pointer to output reference or NULL
+ * @param repo the repository to search in
+ * @param spec the textual specification for an object
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC
+ * or an error code
+ */
+GIT_EXTERN(int) git_revparse_ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec);
+
+/**
+ * Revparse flags. These indicate the intended behavior of the spec passed to
+ * git_revparse.
+ */
+typedef enum {
+ /** The spec targeted a single object. */
+ GIT_REVPARSE_SINGLE = 1 << 0,
+ /** The spec targeted a range of commits. */
+ GIT_REVPARSE_RANGE = 1 << 1,
+ /** The spec used the '...' operator, which invokes special semantics. */
+ GIT_REVPARSE_MERGE_BASE = 1 << 2,
+} git_revparse_mode_t;
+
+/**
+ * Git Revision Spec: output of a `git_revparse` operation
+ */
+typedef struct {
+ /** The left element of the revspec; must be freed by the user */
+ git_object *from;
+ /** The right element of the revspec; must be freed by the user */
+ git_object *to;
+ /** The intent of the revspec (i.e. `git_revparse_mode_t` flags) */
+ unsigned int flags;
+} git_revspec;
+
+/**
+ * Parse a revision string for `from`, `to`, and intent.
+ *
+ * See `man gitrevisions` or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * @param revspec Pointer to an user-allocated git_revspec struct where
+ * the result of the rev-parse will be stored
+ * @param repo the repository to search in
+ * @param spec the rev-parse spec to parse
+ * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code
+ */
+GIT_EXTERN(int) git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec);
+
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_revwalk_h__
+#define INCLUDE_git_revwalk_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+/**
+ * @file git2/revwalk.h
+ * @brief Git revision traversal routines
+ * @defgroup git_revwalk Git revision traversal routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Flags to specify the sorting which a revwalk should perform.
+ */
+typedef enum {
+ /**
+ * Sort the output with the same default time-order method from git.
+ * This is the default sorting for new walkers.
+ */
+ GIT_SORT_NONE = 0,
+
+ /**
+ * Sort the repository contents in topological order (parents before
+ * children); this sorting mode can be combined with time sorting to
+ * produce git's "time-order".
+ */
+ GIT_SORT_TOPOLOGICAL = 1 << 0,
+
+ /**
+ * Sort the repository contents by commit time;
+ * this sorting mode can be combined with
+ * topological sorting.
+ */
+ GIT_SORT_TIME = 1 << 1,
+
+ /**
+ * Iterate through the repository contents in reverse
+ * order; this sorting mode can be combined with
+ * any of the above.
+ */
+ GIT_SORT_REVERSE = 1 << 2,
+} git_sort_t;
+
+/**
+ * Allocate a new revision walker to iterate through a repo.
+ *
+ * This revision walker uses a custom memory pool and an internal
+ * commit cache, so it is relatively expensive to allocate.
+ *
+ * For maximum performance, this revision walker should be
+ * reused for different walks.
+ *
+ * This revision walker is *not* thread safe: it may only be
+ * used to walk a repository on a single thread; however,
+ * it is possible to have several revision walkers in
+ * several different threads walking the same repository.
+ *
+ * @param out pointer to the new revision walker
+ * @param repo the repo to walk through
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_new(git_revwalk **out, git_repository *repo);
+
+/**
+ * Reset the revision walker for reuse.
+ *
+ * This will clear all the pushed and hidden commits, and
+ * leave the walker in a blank state (just like at
+ * creation) ready to receive new commit pushes and
+ * start a new walk.
+ *
+ * The revision walk is automatically reset when a walk
+ * is over.
+ *
+ * @param walker handle to reset.
+ */
+GIT_EXTERN(void) git_revwalk_reset(git_revwalk *walker);
+
+/**
+ * Add a new root for the traversal
+ *
+ * The pushed commit will be marked as one of the roots from which to
+ * start the walk. This commit may not be walked if it or a child is
+ * hidden.
+ *
+ * At least one commit must be pushed onto the walker before a walk
+ * can be started.
+ *
+ * The given id must belong to a committish on the walked
+ * repository.
+ *
+ * @param walk the walker being used for the traversal.
+ * @param id the oid of the commit to start from.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *id);
+
+/**
+ * Push matching references
+ *
+ * The OIDs pointed to by the references that match the given glob
+ * pattern will be pushed to the revision walker.
+ *
+ * A leading 'refs/' is implied if not present as well as a trailing
+ * '/\*' if the glob lacks '?', '\*' or '['.
+ *
+ * Any references matching this glob which do not point to a
+ * committish will be ignored.
+ *
+ * @param walk the walker being used for the traversal
+ * @param glob the glob pattern references should match
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push_glob(git_revwalk *walk, const char *glob);
+
+/**
+ * Push the repository's HEAD
+ *
+ * @param walk the walker being used for the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push_head(git_revwalk *walk);
+
+/**
+ * Mark a commit (and its ancestors) uninteresting for the output.
+ *
+ * The given id must belong to a committish on the walked
+ * repository.
+ *
+ * The resolved commit and all its parents will be hidden from the
+ * output on the revision walk.
+ *
+ * @param walk the walker being used for the traversal.
+ * @param commit_id the oid of commit that will be ignored during the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *commit_id);
+
+/**
+ * Hide matching references.
+ *
+ * The OIDs pointed to by the references that match the given glob
+ * pattern and their ancestors will be hidden from the output on the
+ * revision walk.
+ *
+ * A leading 'refs/' is implied if not present as well as a trailing
+ * '/\*' if the glob lacks '?', '\*' or '['.
+ *
+ * Any references matching this glob which do not point to a
+ * committish will be ignored.
+ *
+ * @param walk the walker being used for the traversal
+ * @param glob the glob pattern references should match
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_glob(git_revwalk *walk, const char *glob);
+
+/**
+ * Hide the repository's HEAD
+ *
+ * @param walk the walker being used for the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_head(git_revwalk *walk);
+
+/**
+ * Push the OID pointed to by a reference
+ *
+ * The reference must point to a committish.
+ *
+ * @param walk the walker being used for the traversal
+ * @param refname the reference to push
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push_ref(git_revwalk *walk, const char *refname);
+
+/**
+ * Hide the OID pointed to by a reference
+ *
+ * The reference must point to a committish.
+ *
+ * @param walk the walker being used for the traversal
+ * @param refname the reference to hide
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname);
+
+/**
+ * Get the next commit from the revision walk.
+ *
+ * The initial call to this method is *not* blocking when
+ * iterating through a repo with a time-sorting mode.
+ *
+ * Iterating with Topological or inverted modes makes the initial
+ * call blocking to preprocess the commit list, but this block should be
+ * mostly unnoticeable on most repositories (topological preprocessing
+ * times at 0.3s on the git.git repo).
+ *
+ * The revision walker is reset when the walk is over.
+ *
+ * @param out Pointer where to store the oid of the next commit
+ * @param walk the walker to pop the commit from.
+ * @return 0 if the next commit was found;
+ * GIT_ITEROVER if there are no commits left to iterate
+ */
+GIT_EXTERN(int) git_revwalk_next(git_oid *out, git_revwalk *walk);
+
+/**
+ * Change the sorting mode when iterating through the
+ * repository's contents.
+ *
+ * Changing the sorting mode resets the walker.
+ *
+ * @param walk the walker being used for the traversal.
+ * @param sort_mode combination of GIT_SORT_XXX flags
+ */
+GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode);
+
+/**
+ * Push and hide the respective endpoints of the given range.
+ *
+ * The range should be of the form
+ * <commit>..<commit>
+ * where each <commit> is in the form accepted by 'git_revparse_single'.
+ * The left-hand commit will be hidden and the right-hand commit pushed.
+ *
+ * @param walk the walker being used for the traversal
+ * @param range the range
+ * @return 0 or an error code
+ *
+ */
+GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range);
+
+/**
+ * Simplify the history by first-parent
+ *
+ * No parents other than the first for each commit will be enqueued.
+ */
+GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk);
+
+
+/**
+ * Free a revision walker previously allocated.
+ *
+ * @param walk traversal handle to close. If NULL nothing occurs.
+ */
+GIT_EXTERN(void) git_revwalk_free(git_revwalk *walk);
+
+/**
+ * Return the repository on which this walker
+ * is operating.
+ *
+ * @param walk the revision walker
+ * @return the repository being walked
+ */
+GIT_EXTERN(git_repository *) git_revwalk_repository(git_revwalk *walk);
+
+/**
+ * This is a callback function that user can provide to hide a
+ * commit and its parents. If the callback function returns non-zero value,
+ * then this commit and its parents will be hidden.
+ *
+ * @param commit_id oid of Commit
+ * @param payload User-specified pointer to data to be passed as data payload
+ */
+typedef int(*git_revwalk_hide_cb)(
+ const git_oid *commit_id,
+ void *payload);
+
+/**
+ * Adds a callback function to hide a commit and its parents
+ *
+ * @param walk the revision walker
+ * @param hide_cb callback function to hide a commit and its parents
+ * @param payload data payload to be passed to callback function
+ */
+GIT_EXTERN(int) git_revwalk_add_hide_cb(
+ git_revwalk *walk,
+ git_revwalk_hide_cb hide_cb,
+ void *payload);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_signature_h__
+#define INCLUDE_git_signature_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/signature.h
+ * @brief Git signature creation
+ * @defgroup git_signature Git signature creation
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new action signature.
+ *
+ * Call `git_signature_free()` to free the data.
+ *
+ * Note: angle brackets ('<' and '>') characters are not allowed
+ * to be used in either the `name` or the `email` parameter.
+ *
+ * @param out new signature, in case of error NULL
+ * @param name name of the person
+ * @param email email of the person
+ * @param time time when the action happened
+ * @param offset timezone offset in minutes for the time
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const char *email, git_time_t time, int offset);
+
+/**
+ * Create a new action signature with a timestamp of 'now'.
+ *
+ * Call `git_signature_free()` to free the data.
+ *
+ * @param out new signature, in case of error NULL
+ * @param name name of the person
+ * @param email email of the person
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email);
+
+/**
+ * Create a new action signature with default user and now timestamp.
+ *
+ * This looks up the user.name and user.email from the configuration and
+ * uses the current time as the timestamp, and creates a new signature
+ * based on that information. It will return GIT_ENOTFOUND if either the
+ * user.name or user.email are not set.
+ *
+ * @param out new signature
+ * @param repo repository pointer
+ * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code
+ */
+GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo);
+
+/**
+ * Create a new signature by parsing the given buffer, which is
+ * expected to be in the format "Real Name <email> timestamp tzoffset",
+ * where `timestamp` is the number of seconds since the Unix epoch and
+ * `tzoffset` is the timezone offset in `hhmm` format (note the lack
+ * of a colon separator).
+ *
+ * @param out new signature
+ * @param buf signature string
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_signature_from_buffer(git_signature **out, const char *buf);
+
+/**
+ * Create a copy of an existing signature. All internal strings are also
+ * duplicated.
+ *
+ * Call `git_signature_free()` to free the data.
+ *
+ * @param dest pointer where to store the copy
+ * @param sig signature to duplicate
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_signature_dup(git_signature **dest, const git_signature *sig);
+
+/**
+ * Free an existing signature.
+ *
+ * Because the signature is not an opaque structure, it is legal to free it
+ * manually, but be sure to free the "name" and "email" strings in addition
+ * to the structure itself.
+ *
+ * @param sig signature to free
+ */
+GIT_EXTERN(void) git_signature_free(git_signature *sig);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_stash_h__
+#define INCLUDE_git_stash_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/stash.h
+ * @brief Git stash management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Stash flags
+ */
+typedef enum {
+ /**
+ * No option, default
+ */
+ GIT_STASH_DEFAULT = 0,
+
+ /**
+ * All changes already added to the index are left intact in
+ * the working directory
+ */
+ GIT_STASH_KEEP_INDEX = (1 << 0),
+
+ /**
+ * All untracked files are also stashed and then cleaned up
+ * from the working directory
+ */
+ GIT_STASH_INCLUDE_UNTRACKED = (1 << 1),
+
+ /**
+ * All ignored files are also stashed and then cleaned up from
+ * the working directory
+ */
+ GIT_STASH_INCLUDE_IGNORED = (1 << 2),
+} git_stash_flags;
+
+/**
+ * Save the local modifications to a new stash.
+ *
+ * @param out Object id of the commit containing the stashed state.
+ * This commit is also the target of the direct reference refs/stash.
+ *
+ * @param repo The owning repository.
+ *
+ * @param stasher The identity of the person performing the stashing.
+ *
+ * @param message Optional description along with the stashed state.
+ *
+ * @param flags Flags to control the stashing process. (see GIT_STASH_* above)
+ *
+ * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash,
+ * or error code.
+ */
+GIT_EXTERN(int) git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *stasher,
+ const char *message,
+ uint32_t flags);
+
+/** Stash application flags. */
+typedef enum {
+ GIT_STASH_APPLY_DEFAULT = 0,
+
+ /* Try to reinstate not only the working tree's changes,
+ * but also the index's changes.
+ */
+ GIT_STASH_APPLY_REINSTATE_INDEX = (1 << 0),
+} git_stash_apply_flags;
+
+typedef enum {
+ GIT_STASH_APPLY_PROGRESS_NONE = 0,
+
+ /** Loading the stashed data from the object database. */
+ GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
+
+ /** The stored index is being analyzed. */
+ GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
+
+ /** The modified files are being analyzed. */
+ GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
+
+ /** The untracked and ignored files are being analyzed. */
+ GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
+
+ /** The untracked files are being written to disk. */
+ GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
+
+ /** The modified files are being written to disk. */
+ GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
+
+ /** The stash was applied successfully. */
+ GIT_STASH_APPLY_PROGRESS_DONE,
+} git_stash_apply_progress_t;
+
+/**
+ * Stash application progress notification function.
+ * Return 0 to continue processing, or a negative value to
+ * abort the stash application.
+ */
+typedef int (*git_stash_apply_progress_cb)(
+ git_stash_apply_progress_t progress,
+ void *payload);
+
+/** Stash application options structure.
+ *
+ * Initialize with the `GIT_STASH_APPLY_OPTIONS_INIT` macro to set
+ * sensible defaults; for example:
+ *
+ * git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
+ */
+typedef struct git_stash_apply_options {
+ unsigned int version;
+
+ /** See `git_stash_apply_flags_t`, above. */
+ git_stash_apply_flags flags;
+
+ /** Options to use when writing files to the working directory. */
+ git_checkout_options checkout_options;
+
+ /** Optional callback to notify the consumer of application progress. */
+ git_stash_apply_progress_cb progress_cb;
+ void *progress_payload;
+} git_stash_apply_options;
+
+#define GIT_STASH_APPLY_OPTIONS_VERSION 1
+#define GIT_STASH_APPLY_OPTIONS_INIT { \
+ GIT_STASH_APPLY_OPTIONS_VERSION, \
+ GIT_STASH_APPLY_DEFAULT, \
+ GIT_CHECKOUT_OPTIONS_INIT }
+
+/**
+ * Initializes a `git_stash_apply_options` with default values. Equivalent to
+ * creating an instance with GIT_STASH_APPLY_OPTIONS_INIT.
+ *
+ * @param opts the `git_stash_apply_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_STASH_APPLY_OPTIONS_INIT` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_stash_apply_init_options(
+ git_stash_apply_options *opts, unsigned int version);
+
+/**
+ * Apply a single stashed state from the stash list.
+ *
+ * If local changes in the working directory conflict with changes in the
+ * stash then GIT_EMERGECONFLICT will be returned. In this case, the index
+ * will always remain unmodified and all files in the working directory will
+ * remain unmodified. However, if you are restoring untracked files or
+ * ignored files and there is a conflict when applying the modified files,
+ * then those files will remain in the working directory.
+ *
+ * If passing the GIT_STASH_APPLY_REINSTATE_INDEX flag and there would be
+ * conflicts when reinstating the index, the function will return
+ * GIT_EMERGECONFLICT and both the working directory and index will be left
+ * unmodified.
+ *
+ * Note that a minimum checkout strategy of `GIT_CHECKOUT_SAFE` is implied.
+ *
+ * @param repo The owning repository.
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ * @param options Options to control how stashes are applied.
+ *
+ * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the
+ * given index, GIT_EMERGECONFLICT if changes exist in the working
+ * directory, or an error code
+ */
+GIT_EXTERN(int) git_stash_apply(
+ git_repository *repo,
+ size_t index,
+ const git_stash_apply_options *options);
+
+/**
+ * This is a callback function you can provide to iterate over all the
+ * stashed states that will be invoked per entry.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ * @param message The stash message.
+ * @param stash_id The commit oid of the stashed state.
+ * @param payload Extra parameter to callback function.
+ * @return 0 to continue iterating or non-zero to stop.
+ */
+typedef int (*git_stash_cb)(
+ size_t index,
+ const char* message,
+ const git_oid *stash_id,
+ void *payload);
+
+/**
+ * Loop over all the stashed states and issue a callback for each one.
+ *
+ * If the callback returns a non-zero value, this will stop looping.
+ *
+ * @param repo Repository where to find the stash.
+ *
+ * @param callback Callback to invoke per found stashed state. The most
+ * recent stash state will be enumerated first.
+ *
+ * @param payload Extra parameter to callback function.
+ *
+ * @return 0 on success, non-zero callback return value, or error code.
+ */
+GIT_EXTERN(int) git_stash_foreach(
+ git_repository *repo,
+ git_stash_cb callback,
+ void *payload);
+
+/**
+ * Remove a single stashed state from the stash list.
+ *
+ * @param repo The owning repository.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ *
+ * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given
+ * index, or error code.
+ */
+GIT_EXTERN(int) git_stash_drop(
+ git_repository *repo,
+ size_t index);
+
+/**
+ * Apply a single stashed state from the stash list and remove it from the list
+ * if successful.
+ *
+ * @param repo The owning repository.
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ * @param options Options to control how stashes are applied.
+ *
+ * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given
+ * index, or error code. (see git_stash_apply() above for details)
+*/
+GIT_EXTERN(int) git_stash_pop(
+ git_repository *repo,
+ size_t index,
+ const git_stash_apply_options *options);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_status_h__
+#define INCLUDE_git_status_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/status.h
+ * @brief Git file status routines
+ * @defgroup git_status Git file status routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Status flags for a single file.
+ *
+ * A combination of these values will be returned to indicate the status of
+ * a file. Status compares the working directory, the index, and the
+ * current HEAD of the repository. The `GIT_STATUS_INDEX` set of flags
+ * represents the status of file in the index relative to the HEAD, and the
+ * `GIT_STATUS_WT` set of flags represent the status of the file in the
+ * working directory relative to the index.
+ */
+typedef enum {
+ GIT_STATUS_CURRENT = 0,
+
+ GIT_STATUS_INDEX_NEW = (1u << 0),
+ GIT_STATUS_INDEX_MODIFIED = (1u << 1),
+ GIT_STATUS_INDEX_DELETED = (1u << 2),
+ GIT_STATUS_INDEX_RENAMED = (1u << 3),
+ GIT_STATUS_INDEX_TYPECHANGE = (1u << 4),
+
+ GIT_STATUS_WT_NEW = (1u << 7),
+ GIT_STATUS_WT_MODIFIED = (1u << 8),
+ GIT_STATUS_WT_DELETED = (1u << 9),
+ GIT_STATUS_WT_TYPECHANGE = (1u << 10),
+ GIT_STATUS_WT_RENAMED = (1u << 11),
+ GIT_STATUS_WT_UNREADABLE = (1u << 12),
+
+ GIT_STATUS_IGNORED = (1u << 14),
+ GIT_STATUS_CONFLICTED = (1u << 15),
+} git_status_t;
+
+/**
+ * Function pointer to receive status on individual files
+ *
+ * `path` is the relative path to the file from the root of the repository.
+ *
+ * `status_flags` is a combination of `git_status_t` values that apply.
+ *
+ * `payload` is the value you passed to the foreach function as payload.
+ */
+typedef int (*git_status_cb)(
+ const char *path, unsigned int status_flags, void *payload);
+
+/**
+ * Select the files on which to report status.
+ *
+ * With `git_status_foreach_ext`, this will control which changes get
+ * callbacks. With `git_status_list_new`, these will control which
+ * changes are included in the list.
+ *
+ * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly
+ * matches `git status --porcelain` regarding which files are
+ * included and in what order.
+ * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index
+ * comparison, not looking at working directory changes.
+ * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to
+ * working directory comparison, not comparing the index to the HEAD.
+ */
+typedef enum {
+ GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
+ GIT_STATUS_SHOW_INDEX_ONLY = 1,
+ GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
+} git_status_show_t;
+
+/**
+ * Flags to control status callbacks
+ *
+ * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made
+ * on untracked files. These will only be made if the workdir files are
+ * included in the status "show" option.
+ * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files get callbacks.
+ * Again, these callbacks will only be made if the workdir files are
+ * included in the status "show" option.
+ * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be
+ * made even on unmodified files.
+ * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be
+ * skipped. This only applies if there are no pending typechanges to
+ * the submodule (either from or to another type).
+ * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in
+ * untracked directories should be included. Normally if an entire
+ * directory is new, then just the top-level directory is included (with
+ * a trailing slash on the entry name). This flag says to include all
+ * of the individual files in the directory instead.
+ * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
+ * should be treated as a literal path, and not as a pathspec pattern.
+ * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of
+ * ignored directories should be included in the status. This is like
+ * doing `git ls-files -o -i --exclude-standard` with core git.
+ * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
+ * should be processed between the head and the index and enables
+ * the GIT_STATUS_INDEX_RENAMED as a possible status flag.
+ * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename
+ * detection should be run between the index and the working directory
+ * and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
+ * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
+ * sensitivity for the file system and forces the output to be in
+ * case-sensitive order
+ * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
+ * sensitivity for the file system and forces the output to be in
+ * case-insensitive order
+ * - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection
+ * should include rewritten files
+ * - GIT_STATUS_OPT_NO_REFRESH bypasses the default status behavior of
+ * doing a "soft" index reload (i.e. reloading the index data if the
+ * file on disk has been modified outside libgit2).
+ * - GIT_STATUS_OPT_UPDATE_INDEX tells libgit2 to refresh the stat cache
+ * in the index for files that are unchanged but have out of date stat
+ * information in the index. It will result in less work being done on
+ * subsequent calls to get status. This is mutually exclusive with the
+ * NO_REFRESH option.
+ *
+ * Calling `git_status_foreach()` is like calling the extended version
+ * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
+ * and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS. Those options are bundled
+ * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline.
+ */
+typedef enum {
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0),
+ GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1),
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2),
+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3),
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
+ GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11),
+ GIT_STATUS_OPT_NO_REFRESH = (1u << 12),
+ GIT_STATUS_OPT_UPDATE_INDEX = (1u << 13),
+ GIT_STATUS_OPT_INCLUDE_UNREADABLE = (1u << 14),
+ GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 15),
+} git_status_opt_t;
+
+#define GIT_STATUS_OPT_DEFAULTS \
+ (GIT_STATUS_OPT_INCLUDE_IGNORED | \
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED | \
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS)
+
+/**
+ * Options to control how `git_status_foreach_ext()` will issue callbacks.
+ *
+ * This structure is set so that zeroing it out will give you relatively
+ * sane defaults.
+ *
+ * The `show` value is one of the `git_status_show_t` constants that
+ * control which files to scan and in what order.
+ *
+ * The `flags` value is an OR'ed combination of the `git_status_opt_t`
+ * values above.
+ *
+ * The `pathspec` is an array of path patterns to match (using
+ * fnmatch-style matching), or just an array of paths to match exactly if
+ * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags.
+ */
+typedef struct {
+ unsigned int version;
+ git_status_show_t show;
+ unsigned int flags;
+ git_strarray pathspec;
+} git_status_options;
+
+#define GIT_STATUS_OPTIONS_VERSION 1
+#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_status_options` with default values. Equivalent to
+ * creating an instance with GIT_STATUS_OPTIONS_INIT.
+ *
+ * @param opts The `git_status_options` instance to initialize.
+ * @param version Version of struct; pass `GIT_STATUS_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_status_init_options(
+ git_status_options *opts,
+ unsigned int version);
+
+/**
+ * A status entry, providing the differences between the file as it exists
+ * in HEAD and the index, and providing the differences between the index
+ * and the working directory.
+ *
+ * The `status` value provides the status flags for this file.
+ *
+ * The `head_to_index` value provides detailed information about the
+ * differences between the file in HEAD and the file in the index.
+ *
+ * The `index_to_workdir` value provides detailed information about the
+ * differences between the file in the index and the file in the
+ * working directory.
+ */
+typedef struct {
+ git_status_t status;
+ git_diff_delta *head_to_index;
+ git_diff_delta *index_to_workdir;
+} git_status_entry;
+
+
+/**
+ * Gather file statuses and run a callback for each one.
+ *
+ * The callback is passed the path of the file, the status (a combination of
+ * the `git_status_t` values above) and the `payload` data pointer passed
+ * into this function.
+ *
+ * If the callback returns a non-zero value, this function will stop looping
+ * and return that value to caller.
+ *
+ * @param repo A repository object
+ * @param callback The function to call on each file
+ * @param payload Pointer to pass through to callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_status_foreach(
+ git_repository *repo,
+ git_status_cb callback,
+ void *payload);
+
+/**
+ * Gather file status information and run callbacks as requested.
+ *
+ * This is an extended version of the `git_status_foreach()` API that
+ * allows for more granular control over which paths will be processed and
+ * in what order. See the `git_status_options` structure for details
+ * about the additional controls that this makes available.
+ *
+ * Note that if a `pathspec` is given in the `git_status_options` to filter
+ * the status, then the results from rename detection (if you enable it) may
+ * not be accurate. To do rename detection properly, this must be called
+ * with no `pathspec` so that all files can be considered.
+ *
+ * @param repo Repository object
+ * @param opts Status options structure
+ * @param callback The function to call on each file
+ * @param payload Pointer to pass through to callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_status_foreach_ext(
+ git_repository *repo,
+ const git_status_options *opts,
+ git_status_cb callback,
+ void *payload);
+
+/**
+ * Get file status for a single file.
+ *
+ * This tries to get status for the filename that you give. If no files
+ * match that name (in either the HEAD, index, or working directory), this
+ * returns GIT_ENOTFOUND.
+ *
+ * If the name matches multiple files (for example, if the `path` names a
+ * directory or if running on a case- insensitive filesystem and yet the
+ * HEAD has two entries that both match the path), then this returns
+ * GIT_EAMBIGUOUS because it cannot give correct results.
+ *
+ * This does not do any sort of rename detection. Renames require a set of
+ * targets and because of the path filtering, there is not enough
+ * information to check renames correctly. To check file status with rename
+ * detection, there is no choice but to do a full `git_status_list_new` and
+ * scan through looking for the path that you are interested in.
+ *
+ * @param status_flags Output combination of git_status_t values for file
+ * @param repo A repository object
+ * @param path The exact path to retrieve status for relative to the
+ * repository working directory
+ * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD,
+ * index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files
+ * or if it refers to a folder, and -1 on other errors.
+ */
+GIT_EXTERN(int) git_status_file(
+ unsigned int *status_flags,
+ git_repository *repo,
+ const char *path);
+
+/**
+ * Gather file status information and populate the `git_status_list`.
+ *
+ * Note that if a `pathspec` is given in the `git_status_options` to filter
+ * the status, then the results from rename detection (if you enable it) may
+ * not be accurate. To do rename detection properly, this must be called
+ * with no `pathspec` so that all files can be considered.
+ *
+ * @param out Pointer to store the status results in
+ * @param repo Repository object
+ * @param opts Status options structure
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_status_list_new(
+ git_status_list **out,
+ git_repository *repo,
+ const git_status_options *opts);
+
+/**
+ * Gets the count of status entries in this list.
+ *
+ * If there are no changes in status (at least according the options given
+ * when the status list was created), this can return 0.
+ *
+ * @param statuslist Existing status list object
+ * @return the number of status entries
+ */
+GIT_EXTERN(size_t) git_status_list_entrycount(
+ git_status_list *statuslist);
+
+/**
+ * Get a pointer to one of the entries in the status list.
+ *
+ * The entry is not modifiable and should not be freed.
+ *
+ * @param statuslist Existing status list object
+ * @param idx Position of the entry
+ * @return Pointer to the entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_status_entry *) git_status_byindex(
+ git_status_list *statuslist,
+ size_t idx);
+
+/**
+ * Free an existing status list
+ *
+ * @param statuslist Existing status list object
+ */
+GIT_EXTERN(void) git_status_list_free(
+ git_status_list *statuslist);
+
+/**
+ * Test if the ignore rules apply to a given file.
+ *
+ * This function checks the ignore rules to see if they would apply to the
+ * given file. This indicates if the file would be ignored regardless of
+ * whether the file is already in the index or committed to the repository.
+ *
+ * One way to think of this is if you were to do "git add ." on the
+ * directory containing the file, would it be added or not?
+ *
+ * @param ignored Boolean returning 0 if the file is not ignored, 1 if it is
+ * @param repo A repository object
+ * @param path The file to check ignores for, rooted at the repo's workdir.
+ * @return 0 if ignore rules could be processed for the file (regardless
+ * of whether it exists or not), or an error < 0 if they could not.
+ */
+GIT_EXTERN(int) git_status_should_ignore(
+ int *ignored,
+ git_repository *repo,
+ const char *path);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+// ISO C9x compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124
+//
+// Copyright (c) 2006-2008 Alexander Chemeris
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. The name of the author may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+// error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+# include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+# define _W64 __w64
+# else
+# define _W64
+# endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+ typedef signed char int8_t;
+ typedef signed short int16_t;
+ typedef signed int int32_t;
+ typedef unsigned char uint8_t;
+ typedef unsigned short uint16_t;
+ typedef unsigned int uint32_t;
+#else
+ typedef signed __int8 int8_t;
+ typedef signed __int16 int16_t;
+ typedef signed __int32 int32_t;
+ typedef unsigned __int8 uint8_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int32 uint32_t;
+#endif
+typedef signed __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t int_least8_t;
+typedef int16_t int_least16_t;
+typedef int32_t int_least32_t;
+typedef int64_t int_least64_t;
+typedef uint8_t uint_least8_t;
+typedef uint16_t uint_least16_t;
+typedef uint32_t uint_least32_t;
+typedef uint64_t uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t int_fast8_t;
+typedef int16_t int_fast16_t;
+typedef int32_t int_fast32_t;
+typedef int64_t int_fast64_t;
+typedef uint8_t uint_fast8_t;
+typedef uint16_t uint_fast16_t;
+typedef uint32_t uint_fast32_t;
+typedef uint64_t uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+ typedef signed __int64 intptr_t;
+ typedef unsigned __int64 uintptr_t;
+#else // _WIN64 ][
+ typedef _W64 signed int intptr_t;
+ typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t intmax_t;
+typedef uint64_t uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN ((int8_t)_I8_MIN)
+#define INT8_MAX _I8_MAX
+#define INT16_MIN ((int16_t)_I16_MIN)
+#define INT16_MAX _I16_MAX
+#define INT32_MIN ((int32_t)_I32_MIN)
+#define INT32_MAX _I32_MAX
+#define INT64_MIN ((int64_t)_I64_MIN)
+#define INT64_MAX _I64_MAX
+#define UINT8_MAX _UI8_MAX
+#define UINT16_MAX _UI16_MAX
+#define UINT32_MAX _UI32_MAX
+#define UINT64_MAX _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN INT8_MIN
+#define INT_LEAST8_MAX INT8_MAX
+#define INT_LEAST16_MIN INT16_MIN
+#define INT_LEAST16_MAX INT16_MAX
+#define INT_LEAST32_MIN INT32_MIN
+#define INT_LEAST32_MAX INT32_MAX
+#define INT_LEAST64_MIN INT64_MIN
+#define INT_LEAST64_MAX INT64_MAX
+#define UINT_LEAST8_MAX UINT8_MAX
+#define UINT_LEAST16_MAX UINT16_MAX
+#define UINT_LEAST32_MAX UINT32_MAX
+#define UINT_LEAST64_MAX UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN INT8_MIN
+#define INT_FAST8_MAX INT8_MAX
+#define INT_FAST16_MIN INT16_MIN
+#define INT_FAST16_MAX INT16_MAX
+#define INT_FAST32_MIN INT32_MIN
+#define INT_FAST32_MAX INT32_MAX
+#define INT_FAST64_MIN INT64_MIN
+#define INT_FAST64_MAX INT64_MAX
+#define UINT_FAST8_MAX UINT8_MAX
+#define UINT_FAST16_MAX UINT16_MAX
+#define UINT_FAST32_MAX UINT32_MAX
+#define UINT_FAST64_MAX UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+# define INTPTR_MIN INT64_MIN
+# define INTPTR_MAX INT64_MAX
+# define UINTPTR_MAX UINT64_MAX
+#else // _WIN64 ][
+# define INTPTR_MIN INT32_MIN
+# define INTPTR_MAX INT32_MAX
+# define UINTPTR_MAX UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN INT64_MIN
+#define INTMAX_MAX INT64_MAX
+#define UINTMAX_MAX UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+# define PTRDIFF_MIN _I64_MIN
+# define PTRDIFF_MAX _I64_MAX
+#else // _WIN64 ][
+# define PTRDIFF_MIN _I32_MIN
+# define PTRDIFF_MAX _I32_MAX
+#endif // _WIN64 ]
+
+#define SIG_ATOMIC_MIN INT_MIN
+#define SIG_ATOMIC_MAX INT_MAX
+
+#ifndef SIZE_MAX // [
+# ifdef _WIN64 // [
+# define SIZE_MAX _UI64_MAX
+# else // _WIN64 ][
+# define SIZE_MAX _UI32_MAX
+# endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+# define WCHAR_MIN 0
+#endif // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+# define WCHAR_MAX _UI16_MAX
+#endif // WCHAR_MAX ]
+
+#define WINT_MIN 0
+#define WINT_MAX _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val) val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val) val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+#define INTMAX_C INT64_C
+#define UINTMAX_C UINT64_C
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+
+#endif // _MSC_STDINT_H_ ]
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_strarray_h__
+#define INCLUDE_git_strarray_h__
+
+#include "common.h"
+
+/**
+ * @file git2/strarray.h
+ * @brief Git string array routines
+ * @defgroup git_strarray Git string array routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Array of strings */
+typedef struct git_strarray {
+ char **strings;
+ size_t count;
+} git_strarray;
+
+/**
+ * Close a string array object
+ *
+ * This method should be called on `git_strarray` objects where the strings
+ * array is allocated and contains allocated strings, such as what you
+ * would get from `git_strarray_copy()`. Not doing so, will result in a
+ * memory leak.
+ *
+ * This does not free the `git_strarray` itself, since the library will
+ * never allocate that object directly itself (it is more commonly embedded
+ * inside another struct or created on the stack).
+ *
+ * @param array git_strarray from which to free string data
+ */
+GIT_EXTERN(void) git_strarray_free(git_strarray *array);
+
+/**
+ * Copy a string array object from source to target.
+ *
+ * Note: target is overwritten and hence should be empty, otherwise its
+ * contents are leaked. Call git_strarray_free() if necessary.
+ *
+ * @param tgt target
+ * @param src source
+ * @return 0 on success, < 0 on allocation failure
+ */
+GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src);
+
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_submodule_h__
+#define INCLUDE_git_submodule_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "remote.h"
+#include "checkout.h"
+
+/**
+ * @file git2/submodule.h
+ * @brief Git submodule management utilities
+ *
+ * Submodule support in libgit2 builds a list of known submodules and keeps
+ * it in the repository. The list is built from the .gitmodules file, the
+ * .git/config file, the index, and the HEAD tree. Items in the working
+ * directory that look like submodules (i.e. a git repo) but are not
+ * mentioned in those places won't be tracked.
+ *
+ * @defgroup git_submodule Git submodule management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Return codes for submodule status.
+ *
+ * A combination of these flags will be returned to describe the status of a
+ * submodule. Depending on the "ignore" property of the submodule, some of
+ * the flags may never be returned because they indicate changes that are
+ * supposed to be ignored.
+ *
+ * Submodule info is contained in 4 places: the HEAD tree, the index, config
+ * files (both .git/config and .gitmodules), and the working directory. Any
+ * or all of those places might be missing information about the submodule
+ * depending on what state the repo is in. We consider all four places to
+ * build the combination of status flags.
+ *
+ * There are four values that are not really status, but give basic info
+ * about what sources of submodule data are available. These will be
+ * returned even if ignore is set to "ALL".
+ *
+ * * IN_HEAD - superproject head contains submodule
+ * * IN_INDEX - superproject index contains submodule
+ * * IN_CONFIG - superproject gitmodules has submodule
+ * * IN_WD - superproject workdir has submodule
+ *
+ * The following values will be returned so long as ignore is not "ALL".
+ *
+ * * INDEX_ADDED - in index, not in head
+ * * INDEX_DELETED - in head, not in index
+ * * INDEX_MODIFIED - index and head don't match
+ * * WD_UNINITIALIZED - workdir contains empty directory
+ * * WD_ADDED - in workdir, not index
+ * * WD_DELETED - in index, not workdir
+ * * WD_MODIFIED - index and workdir head don't match
+ *
+ * The following can only be returned if ignore is "NONE" or "UNTRACKED".
+ *
+ * * WD_INDEX_MODIFIED - submodule workdir index is dirty
+ * * WD_WD_MODIFIED - submodule workdir has modified files
+ *
+ * Lastly, the following will only be returned for ignore "NONE".
+ *
+ * * WD_UNTRACKED - wd contains untracked files
+ */
+typedef enum {
+ GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0),
+ GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1),
+ GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2),
+ GIT_SUBMODULE_STATUS_IN_WD = (1u << 3),
+ GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4),
+ GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5),
+ GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6),
+ GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7),
+ GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8),
+ GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9),
+ GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10),
+ GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11),
+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12),
+ GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),
+} git_submodule_status_t;
+
+#define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu
+#define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u
+#define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u
+
+#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \
+ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_INDEX_UNMODIFIED(S) \
+ (((S) & GIT_SUBMODULE_STATUS__INDEX_FLAGS) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(S) \
+ (((S) & (GIT_SUBMODULE_STATUS__WD_FLAGS & \
+ ~GIT_SUBMODULE_STATUS_WD_UNINITIALIZED)) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_WD_DIRTY(S) \
+ (((S) & (GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED | \
+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED | \
+ GIT_SUBMODULE_STATUS_WD_UNTRACKED)) != 0)
+
+/**
+ * Function pointer to receive each submodule
+ *
+ * @param sm git_submodule currently being visited
+ * @param name name of the submodule
+ * @param payload value you passed to the foreach function as payload
+ * @return 0 on success or error code
+ */
+typedef int (*git_submodule_cb)(
+ git_submodule *sm, const char *name, void *payload);
+
+/**
+ * Submodule update options structure
+ *
+ * Use the GIT_SUBMODULE_UPDATE_OPTIONS_INIT to get the default settings,
+ * like this:
+ *
+ * git_submodule_update_options opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
+ */
+typedef struct git_submodule_update_options {
+ unsigned int version;
+
+ /**
+ * These options are passed to the checkout step. To disable
+ * checkout, set the `checkout_strategy` to
+ * `GIT_CHECKOUT_NONE`. Generally you will want the use
+ * GIT_CHECKOUT_SAFE to update files in the working
+ * directory. Use the `clone_checkout_strategy` field
+ * to set the checkout strategy that will be used in
+ * the case where update needs to clone the repository.
+ */
+ git_checkout_options checkout_opts;
+
+ /**
+ * Options which control the fetch, including callbacks.
+ *
+ * The callbacks to use for reporting fetch progress, and for acquiring
+ * credentials in the event they are needed.
+ */
+ git_fetch_options fetch_opts;
+
+ /**
+ * The checkout strategy to use when the sub repository needs to
+ * be cloned. Use GIT_CHECKOUT_SAFE to create all files
+ * in the working directory for the newly cloned repository.
+ */
+ unsigned int clone_checkout_strategy;
+
+ /**
+ * Allow fetching from the submodule's default remote if the target
+ * commit isn't found. Enabled by default.
+ */
+ int allow_fetch;
+} git_submodule_update_options;
+
+#define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1
+#define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \
+ { GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \
+ { GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
+ GIT_FETCH_OPTIONS_INIT, GIT_CHECKOUT_SAFE, 1 }
+
+/**
+ * Initializes a `git_submodule_update_options` with default values.
+ * Equivalent to creating an instance with GIT_SUBMODULE_UPDATE_OPTIONS_INIT.
+ *
+ * @param opts The `git_submodule_update_options` instance to initialize.
+ * @param version Version of struct; pass `GIT_SUBMODULE_UPDATE_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_submodule_update_init_options(
+ git_submodule_update_options *opts, unsigned int version);
+
+/**
+ * Update a submodule. This will clone a missing submodule and
+ * checkout the subrepository to the commit specified in the index of
+ * the containing repository. If the submodule repository doesn't contain
+ * the target commit (e.g. because fetchRecurseSubmodules isn't set), then
+ * the submodule is fetched using the fetch options supplied in options.
+ *
+ * @param submodule Submodule object
+ * @param init If the submodule is not initialized, setting this flag to true
+ * will initialize the submodule before updating. Otherwise, this will
+ * return an error if attempting to update an uninitialzed repository.
+ * but setting this to true forces them to be updated.
+ * @param options configuration options for the update. If NULL, the
+ * function works as though GIT_SUBMODULE_UPDATE_OPTIONS_INIT was passed.
+ * @return 0 on success, any non-zero return value from a callback
+ * function, or a negative value to indicate an error (use
+ * `giterr_last` for a detailed error message).
+ */
+GIT_EXTERN(int) git_submodule_update(git_submodule *submodule, int init, git_submodule_update_options *options);
+
+/**
+ * Lookup submodule information by name or path.
+ *
+ * Given either the submodule name or path (they are usually the same), this
+ * returns a structure describing the submodule.
+ *
+ * There are two expected error scenarios:
+ *
+ * - The submodule is not mentioned in the HEAD, the index, and the config,
+ * but does "exist" in the working directory (i.e. there is a subdirectory
+ * that appears to be a Git repository). In this case, this function
+ * returns GIT_EEXISTS to indicate a sub-repository exists but not in a
+ * state where a git_submodule can be instantiated.
+ * - The submodule is not mentioned in the HEAD, index, or config and the
+ * working directory doesn't contain a value git repo at that path.
+ * There may or may not be anything else at that path, but nothing that
+ * looks like a submodule. In this case, this returns GIT_ENOTFOUND.
+ *
+ * You must call `git_submodule_free` when done with the submodule.
+ *
+ * @param out Output ptr to submodule; pass NULL to just get return code
+ * @param repo The parent repository
+ * @param name The name of or path to the submodule; trailing slashes okay
+ * @return 0 on success, GIT_ENOTFOUND if submodule does not exist,
+ * GIT_EEXISTS if a repository is found in working directory only,
+ * -1 on other errors.
+ */
+GIT_EXTERN(int) git_submodule_lookup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *name);
+
+/**
+ * Release a submodule
+ *
+ * @param submodule Submodule object
+ */
+GIT_EXTERN(void) git_submodule_free(git_submodule *submodule);
+
+/**
+ * Iterate over all tracked submodules of a repository.
+ *
+ * See the note on `git_submodule` above. This iterates over the tracked
+ * submodules as described therein.
+ *
+ * If you are concerned about items in the working directory that look like
+ * submodules but are not tracked, the diff API will generate a diff record
+ * for workdir items that look like submodules but are not tracked, showing
+ * them as added in the workdir. Also, the status API will treat the entire
+ * subdirectory of a contained git repo as a single GIT_STATUS_WT_NEW item.
+ *
+ * @param repo The repository
+ * @param callback Function to be called with the name of each submodule.
+ * Return a non-zero value to terminate the iteration.
+ * @param payload Extra data to pass to callback
+ * @return 0 on success, -1 on error, or non-zero return value of callback
+ */
+GIT_EXTERN(int) git_submodule_foreach(
+ git_repository *repo,
+ git_submodule_cb callback,
+ void *payload);
+
+/**
+ * Set up a new git submodule for checkout.
+ *
+ * This does "git submodule add" up to the fetch and checkout of the
+ * submodule contents. It preps a new submodule, creates an entry in
+ * .gitmodules and creates an empty initialized repository either at the
+ * given path in the working directory or in .git/modules with a gitlink
+ * from the working directory to the new repo.
+ *
+ * To fully emulate "git submodule add" call this function, then open the
+ * submodule repo and perform the clone step as needed. Lastly, call
+ * `git_submodule_add_finalize()` to wrap up adding the new submodule and
+ * .gitmodules to the index to be ready to commit.
+ *
+ * You must call `git_submodule_free` on the submodule object when done.
+ *
+ * @param out The newly created submodule ready to open for clone
+ * @param repo The repository in which you want to create the submodule
+ * @param url URL for the submodule's remote
+ * @param path Path at which the submodule should be created
+ * @param use_gitlink Should workdir contain a gitlink to the repo in
+ * .git/modules vs. repo directly in workdir.
+ * @return 0 on success, GIT_EEXISTS if submodule already exists,
+ * -1 on other errors.
+ */
+GIT_EXTERN(int) git_submodule_add_setup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *url,
+ const char *path,
+ int use_gitlink);
+
+/**
+ * Resolve the setup of a new git submodule.
+ *
+ * This should be called on a submodule once you have called add setup
+ * and done the clone of the submodule. This adds the .gitmodules file
+ * and the newly cloned submodule to the index to be ready to be committed
+ * (but doesn't actually do the commit).
+ *
+ * @param submodule The submodule to finish adding.
+ */
+GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule);
+
+/**
+ * Add current submodule HEAD commit to index of superproject.
+ *
+ * @param submodule The submodule to add to the index
+ * @param write_index Boolean if this should immediately write the index
+ * file. If you pass this as false, you will have to get the
+ * git_index and explicitly call `git_index_write()` on it to
+ * save the change.
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_submodule_add_to_index(
+ git_submodule *submodule,
+ int write_index);
+
+/**
+ * Get the containing repository for a submodule.
+ *
+ * This returns a pointer to the repository that contains the submodule.
+ * This is a just a reference to the repository that was passed to the
+ * original `git_submodule_lookup()` call, so if that repository has been
+ * freed, then this may be a dangling reference.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to `git_repository`
+ */
+GIT_EXTERN(git_repository *) git_submodule_owner(git_submodule *submodule);
+
+/**
+ * Get the name of submodule.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule name
+ */
+GIT_EXTERN(const char *) git_submodule_name(git_submodule *submodule);
+
+/**
+ * Get the path to the submodule.
+ *
+ * The path is almost always the same as the submodule name, but the
+ * two are actually not required to match.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule path
+ */
+GIT_EXTERN(const char *) git_submodule_path(git_submodule *submodule);
+
+/**
+ * Get the URL for the submodule.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule url
+ */
+GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule);
+
+/**
+ * Resolve a submodule url relative to the given repository.
+ *
+ * @param out buffer to store the absolute submodule url in
+ * @param repo Pointer to repository object
+ * @param url Relative url
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *url);
+
+/**
+* Get the branch for the submodule.
+*
+* @param submodule Pointer to submodule object
+* @return Pointer to the submodule branch
+*/
+GIT_EXTERN(const char *) git_submodule_branch(git_submodule *submodule);
+
+/**
+ * Set the branch for the submodule in the configuration
+ *
+ * After calling this, you may wish to call `git_submodule_sync()` to
+ * write the changes to the checked out submodule repository.
+ *
+ * @param repo the repository to affect
+ * @param name the name of the submodule to configure
+ * @param branch Branch that should be used for the submodule
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_submodule_set_branch(git_repository *repo, const char *name, const char *branch);
+
+/**
+ * Set the URL for the submodule in the configuration
+ *
+ *
+ * After calling this, you may wish to call `git_submodule_sync()` to
+ * write the changes to the checked out submodule repository.
+ *
+ * @param repo the repository to affect
+ * @param name the name of the submodule to configure
+ * @param url URL that should be used for the submodule
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_submodule_set_url(git_repository *repo, const char *name, const char *url);
+
+/**
+ * Get the OID for the submodule in the index.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not in index.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_index_id(git_submodule *submodule);
+
+/**
+ * Get the OID for the submodule in the current HEAD tree.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not in the HEAD.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule);
+
+/**
+ * Get the OID for the submodule in the current working directory.
+ *
+ * This returns the OID that corresponds to looking up 'HEAD' in the checked
+ * out submodule. If there are pending changes in the index or anything
+ * else, this won't notice that. You should call `git_submodule_status()`
+ * for a more complete picture about the state of the working directory.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not checked out.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule);
+
+/**
+ * Get the ignore rule that will be used for the submodule.
+ *
+ * These values control the behavior of `git_submodule_status()` for this
+ * submodule. There are four ignore values:
+ *
+ * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents
+ * of the submodule from a clean checkout to be dirty, including the
+ * addition of untracked files. This is the default if unspecified.
+ * - **GIT_SUBMODULE_IGNORE_UNTRACKED** examines the contents of the
+ * working tree (i.e. call `git_status_foreach()` on the submodule) but
+ * UNTRACKED files will not count as making the submodule dirty.
+ * - **GIT_SUBMODULE_IGNORE_DIRTY** means to only check if the HEAD of the
+ * submodule has moved for status. This is fast since it does not need to
+ * scan the working tree of the submodule at all.
+ * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo.
+ * The working directory will be consider clean so long as there is a
+ * checked out version present.
+ *
+ * @param submodule The submodule to check
+ * @return The current git_submodule_ignore_t valyue what will be used for
+ * this submodule.
+ */
+GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
+ git_submodule *submodule);
+
+/**
+ * Set the ignore rule for the submodule in the configuration
+ *
+ * This does not affect any currently-loaded instances.
+ *
+ * @param repo the repository to affect
+ * @param name the name of the submdule
+ * @param ignore The new value for the ignore rule
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_submodule_set_ignore(
+ git_repository *repo,
+ const char *name,
+ git_submodule_ignore_t ignore);
+
+/**
+ * Get the update rule that will be used for the submodule.
+ *
+ * This value controls the behavior of the `git submodule update` command.
+ * There are four useful values documented with `git_submodule_update_t`.
+ *
+ * @param submodule The submodule to check
+ * @return The current git_submodule_update_t value that will be used
+ * for this submodule.
+ */
+GIT_EXTERN(git_submodule_update_t) git_submodule_update_strategy(
+ git_submodule *submodule);
+
+/**
+ * Set the update rule for the submodule in the configuration
+ *
+ * This setting won't affect any existing instances.
+ *
+ * @param repo the repository to affect
+ * @param name the name of the submodule to configure
+ * @param update The new value to use
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_submodule_set_update(
+ git_repository *repo,
+ const char *name,
+ git_submodule_update_t update);
+
+/**
+ * Read the fetchRecurseSubmodules rule for a submodule.
+ *
+ * This accesses the submodule.<name>.fetchRecurseSubmodules value for
+ * the submodule that controls fetching behavior for the submodule.
+ *
+ * Note that at this time, libgit2 does not honor this setting and the
+ * fetch functionality current ignores submodules.
+ *
+ * @return 0 if fetchRecurseSubmodules is false, 1 if true
+ */
+GIT_EXTERN(git_submodule_recurse_t) git_submodule_fetch_recurse_submodules(
+ git_submodule *submodule);
+
+/**
+ * Set the fetchRecurseSubmodules rule for a submodule in the configuration
+ *
+ * This setting won't affect any existing instances.
+ *
+ * @param repo the repository to affect
+ * @param name the submodule to configure
+ * @param fetch_recurse_submodules Boolean value
+ * @return old value for fetchRecurseSubmodules
+ */
+GIT_EXTERN(int) git_submodule_set_fetch_recurse_submodules(
+ git_repository *repo,
+ const char *name,
+ git_submodule_recurse_t fetch_recurse_submodules);
+
+/**
+ * Copy submodule info into ".git/config" file.
+ *
+ * Just like "git submodule init", this copies information about the
+ * submodule into ".git/config". You can use the accessor functions
+ * above to alter the in-memory git_submodule object and control what
+ * is written to the config, overriding what is in .gitmodules.
+ *
+ * @param submodule The submodule to write into the superproject config
+ * @param overwrite By default, existing entries will not be overwritten,
+ * but setting this to true forces them to be updated.
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite);
+
+/**
+ * Set up the subrepository for a submodule in preparation for clone.
+ *
+ * This function can be called to init and set up a submodule
+ * repository from a submodule in preparation to clone it from
+ * its remote.
+ *
+ * @param out Output pointer to the created git repository.
+ * @param sm The submodule to create a new subrepository from.
+ * @param use_gitlink Should the workdir contain a gitlink to
+ * the repo in .git/modules vs. repo directly in workdir.
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_submodule_repo_init(
+ git_repository **out,
+ const git_submodule *sm,
+ int use_gitlink);
+
+/**
+ * Copy submodule remote info into submodule repo.
+ *
+ * This copies the information about the submodules URL into the checked out
+ * submodule config, acting like "git submodule sync". This is useful if
+ * you have altered the URL for the submodule (or it has been altered by a
+ * fetch of upstream changes) and you need to update your local repo.
+ */
+GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule);
+
+/**
+ * Open the repository for a submodule.
+ *
+ * This is a newly opened repository object. The caller is responsible for
+ * calling `git_repository_free()` on it when done. Multiple calls to this
+ * function will return distinct `git_repository` objects. This will only
+ * work if the submodule is checked out into the working directory.
+ *
+ * @param repo Pointer to the submodule repo which was opened
+ * @param submodule Submodule to be opened
+ * @return 0 on success, <0 if submodule repo could not be opened.
+ */
+GIT_EXTERN(int) git_submodule_open(
+ git_repository **repo,
+ git_submodule *submodule);
+
+/**
+ * Reread submodule info from config, index, and HEAD.
+ *
+ * Call this to reread cached submodule information for this submodule if
+ * you have reason to believe that it has changed.
+ *
+ * @param submodule The submodule to reload
+ * @param force Force reload even if the data doesn't seem out of date
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_reload(git_submodule *submodule, int force);
+
+/**
+ * Get the status for a submodule.
+ *
+ * This looks at a submodule and tries to determine the status. It
+ * will return a combination of the `GIT_SUBMODULE_STATUS` values above.
+ * How deeply it examines the working directory to do this will depend
+ * on the `git_submodule_ignore_t` value for the submodule.
+ *
+ * @param status Combination of `GIT_SUBMODULE_STATUS` flags
+ * @param repo the repository in which to look
+ * @param name name of the submodule
+ * @param ignore the ignore rules to follow
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_status(
+ unsigned int *status,
+ git_repository *repo,
+ const char *name,
+ git_submodule_ignore_t ignore);
+
+/**
+ * Get the locations of submodule information.
+ *
+ * This is a bit like a very lightweight version of `git_submodule_status`.
+ * It just returns a made of the first four submodule status values (i.e.
+ * the ones like GIT_SUBMODULE_STATUS_IN_HEAD, etc) that tell you where the
+ * submodule data comes from (i.e. the HEAD commit, gitmodules file, etc.).
+ * This can be useful if you want to know if the submodule is present in the
+ * working directory at this point in time, etc.
+ *
+ * @param location_status Combination of first four `GIT_SUBMODULE_STATUS` flags
+ * @param submodule Submodule for which to get status
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_location(
+ unsigned int *location_status,
+ git_submodule *submodule);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_commit_h__
+#define INCLUDE_sys_git_commit_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * @file git2/sys/commit.h
+ * @brief Low-level Git commit creation
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create new commit in the repository from a list of `git_oid` values.
+ *
+ * See documentation for `git_commit_create()` for information about the
+ * parameters, as the meaning is identical excepting that `tree` and
+ * `parents` now take `git_oid`. This is a dangerous API in that nor
+ * the `tree`, neither the `parents` list of `git_oid`s are checked for
+ * validity.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_create_from_ids(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ size_t parent_count,
+ const git_oid *parents[]);
+
+/**
+ * Callback function to return parents for commit.
+ *
+ * This is invoked with the count of the number of parents processed so far
+ * along with the user supplied payload. This should return a git_oid of
+ * the next parent or NULL if all parents have been provided.
+ */
+typedef const git_oid *(*git_commit_parent_callback)(size_t idx, void *payload);
+
+/**
+ * Create a new commit in the repository with an callback to supply parents.
+ *
+ * See documentation for `git_commit_create()` for information about the
+ * parameters, as the meaning is identical excepting that `tree` takes a
+ * `git_oid` and doesn't check for validity, and `parent_cb` is invoked
+ * with `parent_payload` and should return `git_oid` values or NULL to
+ * indicate that all parents are accounted for.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_create_from_callback(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_commit_parent_callback parent_cb,
+ void *parent_payload);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_config_backend_h__
+#define INCLUDE_sys_git_config_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/config.h"
+
+/**
+ * @file git2/sys/config.h
+ * @brief Git config backend routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Every iterator must have this struct as its first element, so the
+ * API can talk to it. You'd define your iterator as
+ *
+ * struct my_iterator {
+ * git_config_iterator parent;
+ * ...
+ * }
+ *
+ * and assign `iter->parent.backend` to your `git_config_backend`.
+ */
+struct git_config_iterator {
+ git_config_backend *backend;
+ unsigned int flags;
+
+ /**
+ * Return the current entry and advance the iterator. The
+ * memory belongs to the library.
+ */
+ int (*next)(git_config_entry **entry, git_config_iterator *iter);
+
+ /**
+ * Free the iterator
+ */
+ void (*free)(git_config_iterator *iter);
+};
+
+/**
+ * Generic backend that implements the interface to
+ * access a configuration file
+ */
+struct git_config_backend {
+ unsigned int version;
+ /** True if this backend is for a snapshot */
+ int readonly;
+ struct git_config *cfg;
+
+ /* Open means open the file/database and parse if necessary */
+ int (*open)(struct git_config_backend *, git_config_level_t level);
+ int (*get)(struct git_config_backend *, const char *key, git_config_entry **entry);
+ int (*set)(struct git_config_backend *, const char *key, const char *value);
+ int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value);
+ int (*del)(struct git_config_backend *, const char *key);
+ int (*del_multivar)(struct git_config_backend *, const char *key, const char *regexp);
+ int (*iterator)(git_config_iterator **, struct git_config_backend *);
+ /** Produce a read-only version of this backend */
+ int (*snapshot)(struct git_config_backend **, struct git_config_backend *);
+ /**
+ * Lock this backend.
+ *
+ * Prevent any writes to the data store backing this
+ * backend. Any updates must not be visible to any other
+ * readers.
+ */
+ int (*lock)(struct git_config_backend *);
+ /**
+ * Unlock the data store backing this backend. If success is
+ * true, the changes should be committed, otherwise rolled
+ * back.
+ */
+ int (*unlock)(struct git_config_backend *, int success);
+ void (*free)(struct git_config_backend *);
+};
+#define GIT_CONFIG_BACKEND_VERSION 1
+#define GIT_CONFIG_BACKEND_INIT {GIT_CONFIG_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_config_backend` with default values. Equivalent to
+ * creating an instance with GIT_CONFIG_BACKEND_INIT.
+ *
+ * @param backend the `git_config_backend` struct to initialize.
+ * @param version Version of struct; pass `GIT_CONFIG_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_config_init_backend(
+ git_config_backend *backend,
+ unsigned int version);
+
+/**
+ * Add a generic config file instance to an existing config
+ *
+ * Note that the configuration object will free the file
+ * automatically.
+ *
+ * Further queries on this config object will access each
+ * of the config file instances in order (instances with
+ * a higher priority level will be accessed first).
+ *
+ * @param cfg the configuration to add the file to
+ * @param file the configuration file (backend) to add
+ * @param level the priority level of the backend
+ * @param force if a config file already exists for the given
+ * priority level, replace it
+ * @return 0 on success, GIT_EEXISTS when adding more than one file
+ * for a given priority level (and force_replace set to 0), or error code
+ */
+GIT_EXTERN(int) git_config_add_backend(
+ git_config *cfg,
+ git_config_backend *file,
+ git_config_level_t level,
+ int force);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_diff_h__
+#define INCLUDE_sys_git_diff_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/diff.h"
+#include "git2/status.h"
+
+/**
+ * @file git2/sys/diff.h
+ * @brief Low-level Git diff utilities
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Diff print callback that writes to a git_buf.
+ *
+ * This function is provided not for you to call it directly, but instead
+ * so you can use it as a function pointer to the `git_diff_print` or
+ * `git_patch_print` APIs. When using those APIs, you specify a callback
+ * to actually handle the diff and/or patch data.
+ *
+ * Use this callback to easily write that data to a `git_buf` buffer. You
+ * must pass a `git_buf *` value as the payload to the `git_diff_print`
+ * and/or `git_patch_print` function. The data will be appended to the
+ * buffer (after any existing content).
+ */
+GIT_EXTERN(int) git_diff_print_callback__to_buf(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload); /**< payload must be a `git_buf *` */
+
+/**
+ * Diff print callback that writes to stdio FILE handle.
+ *
+ * This function is provided not for you to call it directly, but instead
+ * so you can use it as a function pointer to the `git_diff_print` or
+ * `git_patch_print` APIs. When using those APIs, you specify a callback
+ * to actually handle the diff and/or patch data.
+ *
+ * Use this callback to easily write that data to a stdio FILE handle. You
+ * must pass a `FILE *` value (such as `stdout` or `stderr` or the return
+ * value from `fopen()`) as the payload to the `git_diff_print`
+ * and/or `git_patch_print` function. If you pass NULL, this will write
+ * data to `stdout`.
+ */
+GIT_EXTERN(int) git_diff_print_callback__to_file_handle(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload); /**< payload must be a `FILE *` */
+
+
+/**
+ * Performance data from diffing
+ */
+typedef struct {
+ unsigned int version;
+ size_t stat_calls; /**< Number of stat() calls performed */
+ size_t oid_calculations; /**< Number of ID calculations */
+} git_diff_perfdata;
+
+#define GIT_DIFF_PERFDATA_VERSION 1
+#define GIT_DIFF_PERFDATA_INIT {GIT_DIFF_PERFDATA_VERSION,0,0}
+
+/**
+ * Get performance data for a diff object.
+ *
+ * @param out Structure to be filled with diff performance data
+ * @param diff Diff to read performance data from
+ * @return 0 for success, <0 for error
+ */
+GIT_EXTERN(int) git_diff_get_perfdata(
+ git_diff_perfdata *out, const git_diff *diff);
+
+/**
+ * Get performance data for diffs from a git_status_list
+ */
+GIT_EXTERN(int) git_status_list_get_perfdata(
+ git_diff_perfdata *out, const git_status_list *status);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_filter_h__
+#define INCLUDE_sys_git_filter_h__
+
+#include "git2/filter.h"
+
+/**
+ * @file git2/sys/filter.h
+ * @brief Git filter backend and plugin routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Look up a filter by name
+ *
+ * @param name The name of the filter
+ * @return Pointer to the filter object or NULL if not found
+ */
+GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
+
+#define GIT_FILTER_CRLF "crlf"
+#define GIT_FILTER_IDENT "ident"
+
+/**
+ * This is priority that the internal CRLF filter will be registered with
+ */
+#define GIT_FILTER_CRLF_PRIORITY 0
+
+/**
+ * This is priority that the internal ident filter will be registered with
+ */
+#define GIT_FILTER_IDENT_PRIORITY 100
+
+/**
+ * This is priority to use with a custom filter to imitate a core Git
+ * filter driver, so that it will be run last on checkout and first on
+ * checkin. You do not have to use this, but it helps compatibility.
+ */
+#define GIT_FILTER_DRIVER_PRIORITY 200
+
+/**
+ * Create a new empty filter list
+ *
+ * Normally you won't use this because `git_filter_list_load` will create
+ * the filter list for you, but you can use this in combination with the
+ * `git_filter_lookup` and `git_filter_list_push` functions to assemble
+ * your own chains of filters.
+ */
+GIT_EXTERN(int) git_filter_list_new(
+ git_filter_list **out,
+ git_repository *repo,
+ git_filter_mode_t mode,
+ uint32_t options);
+
+/**
+ * Add a filter to a filter list with the given payload.
+ *
+ * Normally you won't have to do this because the filter list is created
+ * by calling the "check" function on registered filters when the filter
+ * attributes are set, but this does allow more direct manipulation of
+ * filter lists when desired.
+ *
+ * Note that normally the "check" function can set up a payload for the
+ * filter. Using this function, you can either pass in a payload if you
+ * know the expected payload format, or you can pass NULL. Some filters
+ * may fail with a NULL payload. Good luck!
+ */
+GIT_EXTERN(int) git_filter_list_push(
+ git_filter_list *fl, git_filter *filter, void *payload);
+
+/**
+ * Look up how many filters are in the list
+ *
+ * We will attempt to apply all of these filters to any data passed in,
+ * but note that the filter apply action still has the option of skipping
+ * data that is passed in (for example, the CRLF filter will skip data
+ * that appears to be binary).
+ *
+ * @param fl A filter list
+ * @return The number of filters in the list
+ */
+GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl);
+
+/**
+ * A filter source represents a file/blob to be processed
+ */
+typedef struct git_filter_source git_filter_source;
+
+/**
+ * Get the repository that the source data is coming from.
+ */
+GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src);
+
+/**
+ * Get the path that the source data is coming from.
+ */
+GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src);
+
+/**
+ * Get the file mode of the source file
+ * If the mode is unknown, this will return 0
+ */
+GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src);
+
+/**
+ * Get the OID of the source
+ * If the OID is unknown (often the case with GIT_FILTER_CLEAN) then
+ * this will return NULL.
+ */
+GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src);
+
+/**
+ * Get the git_filter_mode_t to be used
+ */
+GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *src);
+
+/**
+ * Get the combination git_filter_flag_t options to be applied
+ */
+GIT_EXTERN(uint32_t) git_filter_source_flags(const git_filter_source *src);
+
+/**
+ * Initialize callback on filter
+ *
+ * Specified as `filter.initialize`, this is an optional callback invoked
+ * before a filter is first used. It will be called once at most.
+ *
+ * If non-NULL, the filter's `initialize` callback will be invoked right
+ * before the first use of the filter, so you can defer expensive
+ * initialization operations (in case libgit2 is being used in a way that
+ * doesn't need the filter).
+ */
+typedef int (*git_filter_init_fn)(git_filter *self);
+
+/**
+ * Shutdown callback on filter
+ *
+ * Specified as `filter.shutdown`, this is an optional callback invoked
+ * when the filter is unregistered or when libgit2 is shutting down. It
+ * will be called once at most and should release resources as needed.
+ * This may be called even if the `initialize` callback was not made.
+ *
+ * Typically this function will free the `git_filter` object itself.
+ */
+typedef void (*git_filter_shutdown_fn)(git_filter *self);
+
+/**
+ * Callback to decide if a given source needs this filter
+ *
+ * Specified as `filter.check`, this is an optional callback that checks
+ * if filtering is needed for a given source.
+ *
+ * It should return 0 if the filter should be applied (i.e. success),
+ * GIT_PASSTHROUGH if the filter should not be applied, or an error code
+ * to fail out of the filter processing pipeline and return to the caller.
+ *
+ * The `attr_values` will be set to the values of any attributes given in
+ * the filter definition. See `git_filter` below for more detail.
+ *
+ * The `payload` will be a pointer to a reference payload for the filter.
+ * This will start as NULL, but `check` can assign to this pointer for
+ * later use by the `apply` callback. Note that the value should be heap
+ * allocated (not stack), so that it doesn't go away before the `apply`
+ * callback can use it. If a filter allocates and assigns a value to the
+ * `payload`, it will need a `cleanup` callback to free the payload.
+ */
+typedef int (*git_filter_check_fn)(
+ git_filter *self,
+ void **payload, /* points to NULL ptr on entry, may be set */
+ const git_filter_source *src,
+ const char **attr_values);
+
+/**
+ * Callback to actually perform the data filtering
+ *
+ * Specified as `filter.apply`, this is the callback that actually filters
+ * data. If it successfully writes the output, it should return 0. Like
+ * `check`, it can return GIT_PASSTHROUGH to indicate that the filter
+ * doesn't want to run. Other error codes will stop filter processing and
+ * return to the caller.
+ *
+ * The `payload` value will refer to any payload that was set by the
+ * `check` callback. It may be read from or written to as needed.
+ */
+typedef int (*git_filter_apply_fn)(
+ git_filter *self,
+ void **payload, /* may be read and/or set */
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src);
+
+typedef int (*git_filter_stream_fn)(
+ git_writestream **out,
+ git_filter *self,
+ void **payload,
+ const git_filter_source *src,
+ git_writestream *next);
+
+/**
+ * Callback to clean up after filtering has been applied
+ *
+ * Specified as `filter.cleanup`, this is an optional callback invoked
+ * after the filter has been applied. If the `check` or `apply` callbacks
+ * allocated a `payload` to keep per-source filter state, use this
+ * callback to free that payload and release resources as required.
+ */
+typedef void (*git_filter_cleanup_fn)(
+ git_filter *self,
+ void *payload);
+
+/**
+ * Filter structure used to register custom filters.
+ *
+ * To associate extra data with a filter, allocate extra data and put the
+ * `git_filter` struct at the start of your data buffer, then cast the
+ * `self` pointer to your larger structure when your callback is invoked.
+ */
+struct git_filter {
+ /** The `version` field should be set to `GIT_FILTER_VERSION`. */
+ unsigned int version;
+
+ /**
+ * A whitespace-separated list of attribute names to check for this
+ * filter (e.g. "eol crlf text"). If the attribute name is bare, it
+ * will be simply loaded and passed to the `check` callback. If it
+ * has a value (i.e. "name=value"), the attribute must match that
+ * value for the filter to be applied. The value may be a wildcard
+ * (eg, "name=*"), in which case the filter will be invoked for any
+ * value for the given attribute name. See the attribute parameter
+ * of the `check` callback for the attribute value that was specified.
+ */
+ const char *attributes;
+
+ /** Called when the filter is first used for any file. */
+ git_filter_init_fn initialize;
+
+ /** Called when the filter is removed or unregistered from the system. */
+ git_filter_shutdown_fn shutdown;
+
+ /**
+ * Called to determine whether the filter should be invoked for a
+ * given file. If this function returns `GIT_PASSTHROUGH` then the
+ * `apply` function will not be invoked and the contents will be passed
+ * through unmodified.
+ */
+ git_filter_check_fn check;
+
+ /**
+ * Called to actually apply the filter to file contents. If this
+ * function returns `GIT_PASSTHROUGH` then the contents will be passed
+ * through unmodified.
+ */
+ git_filter_apply_fn apply;
+
+ /**
+ * Called to apply the filter in a streaming manner. If this is not
+ * specified then the system will call `apply` with the whole buffer.
+ */
+ git_filter_stream_fn stream;
+
+ /** Called when the system is done filtering for a file. */
+ git_filter_cleanup_fn cleanup;
+};
+
+#define GIT_FILTER_VERSION 1
+
+/**
+ * Register a filter under a given name with a given priority.
+ *
+ * As mentioned elsewhere, the initialize callback will not be invoked
+ * immediately. It is deferred until the filter is used in some way.
+ *
+ * A filter's attribute checks and `check` and `apply` callbacks will be
+ * issued in order of `priority` on smudge (to workdir), and in reverse
+ * order of `priority` on clean (to odb).
+ *
+ * Two filters are preregistered with libgit2:
+ * - GIT_FILTER_CRLF with priority 0
+ * - GIT_FILTER_IDENT with priority 100
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
+ *
+ * @param name A name by which the filter can be referenced. Attempting
+ * to register with an in-use name will return GIT_EEXISTS.
+ * @param filter The filter definition. This pointer will be stored as is
+ * by libgit2 so it must be a durable allocation (either static
+ * or on the heap).
+ * @param priority The priority for filter application
+ * @return 0 on successful registry, error code <0 on failure
+ */
+GIT_EXTERN(int) git_filter_register(
+ const char *name, git_filter *filter, int priority);
+
+/**
+ * Remove the filter with the given name
+ *
+ * Attempting to remove the builtin libgit2 filters is not permitted and
+ * will return an error.
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
+ *
+ * @param name The name under which the filter was registered
+ * @return 0 on success, error code <0 on failure
+ */
+GIT_EXTERN(int) git_filter_unregister(const char *name);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_hashsig_h__
+#define INCLUDE_sys_hashsig_h__
+
+#include "git2/common.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Similarity signature of arbitrary text content based on line hashes
+ */
+typedef struct git_hashsig git_hashsig;
+
+/**
+ * Options for hashsig computation
+ *
+ * The options GIT_HASHSIG_NORMAL, GIT_HASHSIG_IGNORE_WHITESPACE,
+ * GIT_HASHSIG_SMART_WHITESPACE are exclusive and should not be combined.
+ */
+typedef enum {
+ /**
+ * Use all data
+ */
+ GIT_HASHSIG_NORMAL = 0,
+
+ /**
+ * Ignore whitespace
+ */
+ GIT_HASHSIG_IGNORE_WHITESPACE = (1 << 0),
+
+ /**
+ * Ignore \r and all space after \n
+ */
+ GIT_HASHSIG_SMART_WHITESPACE = (1 << 1),
+
+ /**
+ * Allow hashing of small files
+ */
+ GIT_HASHSIG_ALLOW_SMALL_FILES = (1 << 2)
+} git_hashsig_option_t;
+
+/**
+ * Compute a similarity signature for a text buffer
+ *
+ * If you have passed the option GIT_HASHSIG_IGNORE_WHITESPACE, then the
+ * whitespace will be removed from the buffer while it is being processed,
+ * modifying the buffer in place. Sorry about that!
+ *
+ * @param out The computed similarity signature.
+ * @param buf The input buffer.
+ * @param buflen The input buffer size.
+ * @param opts The signature computation options (see above).
+ * @return 0 on success, GIT_EBUFS if the buffer doesn't contain enough data to
+ * compute a valid signature (unless GIT_HASHSIG_ALLOW_SMALL_FILES is set), or
+ * error code.
+ */
+GIT_EXTERN(int) git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts);
+
+/**
+ * Compute a similarity signature for a text file
+ *
+ * This walks through the file, only loading a maximum of 4K of file data at
+ * a time. Otherwise, it acts just like `git_hashsig_create`.
+ *
+ * @param out The computed similarity signature.
+ * @param path The path to the input file.
+ * @param opts The signature computation options (see above).
+ * @return 0 on success, GIT_EBUFS if the buffer doesn't contain enough data to
+ * compute a valid signature (unless GIT_HASHSIG_ALLOW_SMALL_FILES is set), or
+ * error code.
+ */
+GIT_EXTERN(int) git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts);
+
+/**
+ * Release memory for a content similarity signature
+ *
+ * @param sig The similarity signature to free.
+ */
+GIT_EXTERN(void) git_hashsig_free(git_hashsig *sig);
+
+/**
+ * Measure similarity score between two similarity signatures
+ *
+ * @param a The first similarity signature to compare.
+ * @param b The second similarity signature to compare.
+ * @return [0 to 100] on success as the similarity score, or error code.
+ */
+GIT_EXTERN(int) git_hashsig_compare(
+ const git_hashsig *a,
+ const git_hashsig *b);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_index_h__
+#define INCLUDE_sys_git_index_h__
+
+/**
+ * @file git2/sys/index.h
+ * @brief Low-level Git index manipulation routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Representation of a rename conflict entry in the index. */
+typedef struct git_index_name_entry {
+ char *ancestor;
+ char *ours;
+ char *theirs;
+} git_index_name_entry;
+
+/** Representation of a resolve undo entry in the index. */
+typedef struct git_index_reuc_entry {
+ uint32_t mode[3];
+ git_oid oid[3];
+ char *path;
+} git_index_reuc_entry;
+
+/** @name Conflict Name entry functions
+ *
+ * These functions work on rename conflict entries.
+ */
+/**@{*/
+
+/**
+ * Get the count of filename conflict entries currently in the index.
+ *
+ * @param index an existing index object
+ * @return integer of count of current filename conflict entries
+ */
+GIT_EXTERN(size_t) git_index_name_entrycount(git_index *index);
+
+/**
+ * Get a filename conflict entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the filename conflict entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_name_entry *) git_index_name_get_byindex(
+ git_index *index, size_t n);
+
+/**
+ * Record the filenames involved in a rename conflict.
+ *
+ * @param index an existing index object
+ * @param ancestor the path of the file as it existed in the ancestor
+ * @param ours the path of the file as it existed in our tree
+ * @param theirs the path of the file as it existed in their tree
+ */
+GIT_EXTERN(int) git_index_name_add(git_index *index,
+ const char *ancestor, const char *ours, const char *theirs);
+
+/**
+ * Remove all filename conflict entries.
+ *
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_name_clear(git_index *index);
+
+/**@}*/
+
+/** @name Resolve Undo (REUC) index entry manipulation.
+ *
+ * These functions work on the Resolve Undo index extension and contains
+ * data about the original files that led to a merge conflict.
+ */
+/**@{*/
+
+/**
+ * Get the count of resolve undo entries currently in the index.
+ *
+ * @param index an existing index object
+ * @return integer of count of current resolve undo entries
+ */
+GIT_EXTERN(size_t) git_index_reuc_entrycount(git_index *index);
+
+/**
+ * Finds the resolve undo entry that points to the given path in the Git
+ * index.
+ *
+ * @param at_pos the address to which the position of the reuc entry is written (optional)
+ * @param index an existing index object
+ * @param path path to search
+ * @return 0 if found, < 0 otherwise (GIT_ENOTFOUND)
+ */
+GIT_EXTERN(int) git_index_reuc_find(size_t *at_pos, git_index *index, const char *path);
+
+/**
+ * Get a resolve undo entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @return the resolve undo entry; NULL if not found
+ */
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path);
+
+/**
+ * Get a resolve undo entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the resolve undo entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n);
+
+/**
+ * Adds a resolve undo entry for a file based on the given parameters.
+ *
+ * The resolve undo entry contains the OIDs of files that were involved
+ * in a merge conflict after the conflict has been resolved. This allows
+ * conflicts to be re-resolved later.
+ *
+ * If there exists a resolve undo entry for the given path in the index,
+ * it will be removed.
+ *
+ * This method will fail in bare index instances.
+ *
+ * @param index an existing index object
+ * @param path filename to add
+ * @param ancestor_mode mode of the ancestor file
+ * @param ancestor_id oid of the ancestor file
+ * @param our_mode mode of our file
+ * @param our_id oid of our file
+ * @param their_mode mode of their file
+ * @param their_id oid of their file
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, const git_oid *ancestor_id,
+ int our_mode, const git_oid *our_id,
+ int their_mode, const git_oid *their_id);
+
+/**
+ * Remove an resolve undo entry from the index
+ *
+ * @param index an existing index object
+ * @param n position of the resolve undo entry to remove
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n);
+
+/**
+ * Remove all resolve undo entries from the index
+ *
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_reuc_clear(git_index *index);
+
+/**@}*/
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_odb_mempack_h__
+#define INCLUDE_sys_git_odb_mempack_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+
+/**
+ * @file git2/sys/mempack.h
+ * @brief Custom ODB backend that permits packing objects in-memory
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Instantiate a new mempack backend.
+ *
+ * The backend must be added to an existing ODB with the highest
+ * priority.
+ *
+ * git_mempack_new(&mempacker);
+ * git_repository_odb(&odb, repository);
+ * git_odb_add_backend(odb, mempacker, 999);
+ *
+ * Once the backend has been loaded, all writes to the ODB will
+ * instead be queued in memory, and can be finalized with
+ * `git_mempack_dump`.
+ *
+ * Subsequent reads will also be served from the in-memory store
+ * to ensure consistency, until the memory store is dumped.
+ *
+ * @param out Poiter where to store the ODB backend
+ * @return 0 on success; error code otherwise
+ */
+int git_mempack_new(git_odb_backend **out);
+
+/**
+ * Dump all the queued in-memory writes to a packfile.
+ *
+ * The contents of the packfile will be stored in the given buffer.
+ * It is the caller's responsibility to ensure that the generated
+ * packfile is available to the repository (e.g. by writing it
+ * to disk, or doing something crazy like distributing it across
+ * several copies of the repository over a network).
+ *
+ * Once the generated packfile is available to the repository,
+ * call `git_mempack_reset` to cleanup the memory store.
+ *
+ * Calling `git_mempack_reset` before the packfile has been
+ * written to disk will result in an inconsistent repository
+ * (the objects in the memory store won't be accessible).
+ *
+ * @param pack Buffer where to store the raw packfile
+ * @param repo The active repository where the backend is loaded
+ * @param backend The mempack backend
+ * @return 0 on success; error code otherwise
+ */
+int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backend);
+
+/**
+ * Reset the memory packer by clearing all the queued objects.
+ *
+ * This assumes that `git_mempack_dump` has been called before to
+ * store all the queued objects into a single packfile.
+ *
+ * Alternatively, call `reset` without a previous dump to "undo"
+ * all the recently written objects, giving transaction-like
+ * semantics to the Git repository.
+ *
+ * @param backend The mempack backend
+ */
+void git_mempack_reset(git_odb_backend *backend);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_merge_h__
+#define INCLUDE_sys_git_merge_h__
+
+/**
+ * @file git2/sys/merge.h
+ * @brief Git merge driver backend and plugin routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+typedef struct git_merge_driver git_merge_driver;
+
+/**
+ * Look up a merge driver by name
+ *
+ * @param name The name of the merge driver
+ * @return Pointer to the merge driver object or NULL if not found
+ */
+GIT_EXTERN(git_merge_driver *) git_merge_driver_lookup(const char *name);
+
+#define GIT_MERGE_DRIVER_TEXT "text"
+#define GIT_MERGE_DRIVER_BINARY "binary"
+#define GIT_MERGE_DRIVER_UNION "union"
+
+/**
+ * A merge driver source represents the file to be merged
+ */
+typedef struct git_merge_driver_source git_merge_driver_source;
+
+/** Get the repository that the source data is coming from. */
+GIT_EXTERN(git_repository *) git_merge_driver_source_repo(
+ const git_merge_driver_source *src);
+
+/** Gets the ancestor of the file to merge. */
+GIT_EXTERN(git_index_entry *) git_merge_driver_source_ancestor(
+ const git_merge_driver_source *src);
+
+/** Gets the ours side of the file to merge. */
+GIT_EXTERN(git_index_entry *) git_merge_driver_source_ours(
+ const git_merge_driver_source *src);
+
+/** Gets the theirs side of the file to merge. */
+GIT_EXTERN(git_index_entry *) git_merge_driver_source_theirs(
+ const git_merge_driver_source *src);
+
+/** Gets the merge file options that the merge was invoked with */
+GIT_EXTERN(git_merge_file_options *) git_merge_driver_source_file_options(
+ const git_merge_driver_source *src);
+
+
+/**
+ * Initialize callback on merge driver
+ *
+ * Specified as `driver.initialize`, this is an optional callback invoked
+ * before a merge driver is first used. It will be called once at most
+ * per library lifetime.
+ *
+ * If non-NULL, the merge driver's `initialize` callback will be invoked
+ * right before the first use of the driver, so you can defer expensive
+ * initialization operations (in case libgit2 is being used in a way that
+ * doesn't need the merge driver).
+ */
+typedef int (*git_merge_driver_init_fn)(git_merge_driver *self);
+
+/**
+ * Shutdown callback on merge driver
+ *
+ * Specified as `driver.shutdown`, this is an optional callback invoked
+ * when the merge driver is unregistered or when libgit2 is shutting down.
+ * It will be called once at most and should release resources as needed.
+ * This may be called even if the `initialize` callback was not made.
+ *
+ * Typically this function will free the `git_merge_driver` object itself.
+ */
+typedef void (*git_merge_driver_shutdown_fn)(git_merge_driver *self);
+
+/**
+ * Callback to perform the merge.
+ *
+ * Specified as `driver.apply`, this is the callback that actually does the
+ * merge. If it can successfully perform a merge, it should populate
+ * `path_out` with a pointer to the filename to accept, `mode_out` with
+ * the resultant mode, and `merged_out` with the buffer of the merged file
+ * and then return 0. If the driver returns `GIT_PASSTHROUGH`, then the
+ * default merge driver should instead be run. It can also return
+ * `GIT_EMERGECONFLICT` if the driver is not able to produce a merge result,
+ * and the file will remain conflicted. Any other errors will fail and
+ * return to the caller.
+ *
+ * The `filter_name` contains the name of the filter that was invoked, as
+ * specified by the file's attributes.
+ *
+ * The `src` contains the data about the file to be merged.
+ */
+typedef int (*git_merge_driver_apply_fn)(
+ git_merge_driver *self,
+ const char **path_out,
+ uint32_t *mode_out,
+ git_buf *merged_out,
+ const char *filter_name,
+ const git_merge_driver_source *src);
+
+/**
+ * Merge driver structure used to register custom merge drivers.
+ *
+ * To associate extra data with a driver, allocate extra data and put the
+ * `git_merge_driver` struct at the start of your data buffer, then cast
+ * the `self` pointer to your larger structure when your callback is invoked.
+ */
+struct git_merge_driver {
+ /** The `version` should be set to `GIT_MERGE_DRIVER_VERSION`. */
+ unsigned int version;
+
+ /** Called when the merge driver is first used for any file. */
+ git_merge_driver_init_fn initialize;
+
+ /** Called when the merge driver is unregistered from the system. */
+ git_merge_driver_shutdown_fn shutdown;
+
+ /**
+ * Called to merge the contents of a conflict. If this function
+ * returns `GIT_PASSTHROUGH` then the default (`text`) merge driver
+ * will instead be invoked. If this function returns
+ * `GIT_EMERGECONFLICT` then the file will remain conflicted.
+ */
+ git_merge_driver_apply_fn apply;
+};
+
+#define GIT_MERGE_DRIVER_VERSION 1
+
+/**
+ * Register a merge driver under a given name.
+ *
+ * As mentioned elsewhere, the initialize callback will not be invoked
+ * immediately. It is deferred until the driver is used in some way.
+ *
+ * Currently the merge driver registry is not thread safe, so any
+ * registering or deregistering of merge drivers must be done outside of
+ * any possible usage of the drivers (i.e. during application setup or
+ * shutdown).
+ *
+ * @param name The name of this driver to match an attribute. Attempting
+ * to register with an in-use name will return GIT_EEXISTS.
+ * @param driver The merge driver definition. This pointer will be stored
+ * as is by libgit2 so it must be a durable allocation (either
+ * static or on the heap).
+ * @return 0 on successful registry, error code <0 on failure
+ */
+GIT_EXTERN(int) git_merge_driver_register(
+ const char *name, git_merge_driver *driver);
+
+/**
+ * Remove the merge driver with the given name.
+ *
+ * Attempting to remove the builtin libgit2 merge drivers is not permitted
+ * and will return an error.
+ *
+ * Currently the merge driver registry is not thread safe, so any
+ * registering or deregistering of drivers must be done outside of any
+ * possible usage of the drivers (i.e. during application setup or shutdown).
+ *
+ * @param name The name under which the merge driver was registered
+ * @return 0 on success, error code <0 on failure
+ */
+GIT_EXTERN(int) git_merge_driver_unregister(const char *name);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_odb_backend_h__
+#define INCLUDE_sys_git_odb_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+
+/**
+ * @file git2/sys/backend.h
+ * @brief Git custom backend implementors functions
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * An instance for a custom backend
+ */
+struct git_odb_backend {
+ unsigned int version;
+ git_odb *odb;
+
+ /* read and read_prefix each return to libgit2 a buffer which
+ * will be freed later. The buffer should be allocated using
+ * the function git_odb_backend_malloc to ensure that it can
+ * be safely freed later. */
+ int (* read)(
+ void **, size_t *, git_otype *, git_odb_backend *, const git_oid *);
+
+ /* To find a unique object given a prefix of its oid. The oid given
+ * must be so that the remaining (GIT_OID_HEXSZ - len)*4 bits are 0s.
+ */
+ int (* read_prefix)(
+ git_oid *, void **, size_t *, git_otype *,
+ git_odb_backend *, const git_oid *, size_t);
+
+ int (* read_header)(
+ size_t *, git_otype *, git_odb_backend *, const git_oid *);
+
+ /**
+ * Write an object into the backend. The id of the object has
+ * already been calculated and is passed in.
+ */
+ int (* write)(
+ git_odb_backend *, const git_oid *, const void *, size_t, git_otype);
+
+ int (* writestream)(
+ git_odb_stream **, git_odb_backend *, git_off_t, git_otype);
+
+ int (* readstream)(
+ git_odb_stream **, git_odb_backend *, const git_oid *);
+
+ int (* exists)(
+ git_odb_backend *, const git_oid *);
+
+ int (* exists_prefix)(
+ git_oid *, git_odb_backend *, const git_oid *, size_t);
+
+ /**
+ * If the backend implements a refreshing mechanism, it should be exposed
+ * through this endpoint. Each call to `git_odb_refresh()` will invoke it.
+ *
+ * However, the backend implementation should try to stay up-to-date as much
+ * as possible by itself as libgit2 will not automatically invoke
+ * `git_odb_refresh()`. For instance, a potential strategy for the backend
+ * implementation to achieve this could be to internally invoke this
+ * endpoint on failed lookups (ie. `exists()`, `read()`, `read_header()`).
+ */
+ int (* refresh)(git_odb_backend *);
+
+ int (* foreach)(
+ git_odb_backend *, git_odb_foreach_cb cb, void *payload);
+
+ int (* writepack)(
+ git_odb_writepack **, git_odb_backend *, git_odb *odb,
+ git_transfer_progress_cb progress_cb, void *progress_payload);
+
+ /**
+ * "Freshens" an already existing object, updating its last-used
+ * time. This occurs when `git_odb_write` was called, but the
+ * object already existed (and will not be re-written). The
+ * underlying implementation may want to update last-used timestamps.
+ *
+ * If callers implement this, they should return `0` if the object
+ * exists and was freshened, and non-zero otherwise.
+ */
+ int (* freshen)(git_odb_backend *, const git_oid *);
+
+ /**
+ * Frees any resources held by the odb (including the `git_odb_backend`
+ * itself). An odb backend implementation must provide this function.
+ */
+ void (* free)(git_odb_backend *);
+};
+
+#define GIT_ODB_BACKEND_VERSION 1
+#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_odb_backend` with default values. Equivalent to
+ * creating an instance with GIT_ODB_BACKEND_INIT.
+ *
+ * @param backend the `git_odb_backend` struct to initialize.
+ * @param version Version the struct; pass `GIT_ODB_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_odb_init_backend(
+ git_odb_backend *backend,
+ unsigned int version);
+
+GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_openssl_h__
+#define INCLUDE_git_openssl_h__
+
+#include "git2/common.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Initialize the OpenSSL locks
+ *
+ * OpenSSL requires the application to determine how it performs
+ * locking.
+ *
+ * This is a last-resort convenience function which libgit2 provides for
+ * allocating and initializing the locks as well as setting the
+ * locking function to use the system's native locking functions.
+ *
+ * The locking function will be cleared and the memory will be freed
+ * when you call git_threads_sutdown().
+ *
+ * If your programming language has an OpenSSL package/bindings, it
+ * likely sets up locking. You should very strongly prefer that over
+ * this function.
+ *
+ * @return 0 on success, -1 if there are errors or if libgit2 was not
+ * built with OpenSSL and threading support.
+ */
+GIT_EXTERN(int) git_openssl_set_locking(void);
+
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_refdb_backend_h__
+#define INCLUDE_sys_git_refdb_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * @file git2/refdb_backend.h
+ * @brief Git custom refs backend functions
+ * @defgroup git_refdb_backend Git custom refs backend API
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+
+/**
+ * Every backend's iterator must have a pointer to itself as the first
+ * element, so the API can talk to it. You'd define your iterator as
+ *
+ * struct my_iterator {
+ * git_reference_iterator parent;
+ * ...
+ * }
+ *
+ * and assign `iter->parent.backend` to your `git_refdb_backend`.
+ */
+struct git_reference_iterator {
+ git_refdb *db;
+
+ /**
+ * Return the current reference and advance the iterator.
+ */
+ int (*next)(
+ git_reference **ref,
+ git_reference_iterator *iter);
+
+ /**
+ * Return the name of the current reference and advance the iterator
+ */
+ int (*next_name)(
+ const char **ref_name,
+ git_reference_iterator *iter);
+
+ /**
+ * Free the iterator
+ */
+ void (*free)(
+ git_reference_iterator *iter);
+};
+
+/** An instance for a custom backend */
+struct git_refdb_backend {
+ unsigned int version;
+
+ /**
+ * Queries the refdb backend to determine if the given ref_name
+ * exists. A refdb implementation must provide this function.
+ */
+ int (*exists)(
+ int *exists,
+ git_refdb_backend *backend,
+ const char *ref_name);
+
+ /**
+ * Queries the refdb backend for a given reference. A refdb
+ * implementation must provide this function.
+ */
+ int (*lookup)(
+ git_reference **out,
+ git_refdb_backend *backend,
+ const char *ref_name);
+
+ /**
+ * Allocate an iterator object for the backend.
+ *
+ * A refdb implementation must provide this function.
+ */
+ int (*iterator)(
+ git_reference_iterator **iter,
+ struct git_refdb_backend *backend,
+ const char *glob);
+
+ /*
+ * Writes the given reference to the refdb. A refdb implementation
+ * must provide this function.
+ */
+ int (*write)(git_refdb_backend *backend,
+ const git_reference *ref, int force,
+ const git_signature *who, const char *message,
+ const git_oid *old, const char *old_target);
+
+ int (*rename)(
+ git_reference **out, git_refdb_backend *backend,
+ const char *old_name, const char *new_name, int force,
+ const git_signature *who, const char *message);
+
+ /**
+ * Deletes the given reference (and if necessary its reflog)
+ * from the refdb. A refdb implementation must provide this
+ * function.
+ */
+ int (*del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target);
+
+ /**
+ * Suggests that the given refdb compress or optimize its references.
+ * This mechanism is implementation specific. (For on-disk reference
+ * databases, this may pack all loose references.) A refdb
+ * implementation may provide this function; if it is not provided,
+ * nothing will be done.
+ */
+ int (*compress)(git_refdb_backend *backend);
+
+ /**
+ * Query whether a particular reference has a log (may be empty)
+ */
+ int (*has_log)(git_refdb_backend *backend, const char *refname);
+
+ /**
+ * Make sure a particular reference will have a reflog which
+ * will be appended to on writes.
+ */
+ int (*ensure_log)(git_refdb_backend *backend, const char *refname);
+
+ /**
+ * Frees any resources held by the refdb (including the `git_refdb_backend`
+ * itself). A refdb backend implementation must provide this function.
+ */
+ void (*free)(git_refdb_backend *backend);
+
+ /**
+ * Read the reflog for the given reference name.
+ */
+ int (*reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name);
+
+ /**
+ * Write a reflog to disk.
+ */
+ int (*reflog_write)(git_refdb_backend *backend, git_reflog *reflog);
+
+ /**
+ * Rename a reflog
+ */
+ int (*reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name);
+
+ /**
+ * Remove a reflog.
+ */
+ int (*reflog_delete)(git_refdb_backend *backend, const char *name);
+
+ /**
+ * Lock a reference. The opaque parameter will be passed to the unlock function
+ */
+ int (*lock)(void **payload_out, git_refdb_backend *backend, const char *refname);
+
+ /**
+ * Unlock a reference. Only one of target or symbolic_target
+ * will be set. success indicates whether to update the
+ * reference or discard the lock (if it's false)
+ */
+ int (*unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog,
+ const git_reference *ref, const git_signature *sig, const char *message);
+};
+
+#define GIT_REFDB_BACKEND_VERSION 1
+#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_refdb_backend` with default values. Equivalent to
+ * creating an instance with GIT_REFDB_BACKEND_INIT.
+ *
+ * @param backend the `git_refdb_backend` struct to initialize
+ * @param version Version of struct; pass `GIT_REFDB_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_refdb_init_backend(
+ git_refdb_backend *backend,
+ unsigned int version);
+
+/**
+ * Constructors for default filesystem-based refdb backend
+ *
+ * Under normal usage, this is called for you when the repository is
+ * opened / created, but you can use this to explicitly construct a
+ * filesystem refdb backend for a repository.
+ *
+ * @param backend_out Output pointer to the git_refdb_backend object
+ * @param repo Git repository to access
+ * @return 0 on success, <0 error code on failure
+ */
+GIT_EXTERN(int) git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repo);
+
+/**
+ * Sets the custom backend to an existing reference DB
+ *
+ * The `git_refdb` will take ownership of the `git_refdb_backend` so you
+ * should NOT free it after calling this function.
+ *
+ * @param refdb database to add the backend to
+ * @param backend pointer to a git_refdb_backend instance
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_refdb_set_backend(
+ git_refdb *refdb,
+ git_refdb_backend *backend);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_reflog_h__
+#define INCLUDE_sys_git_reflog_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+GIT_BEGIN_DECL
+
+GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void);
+GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_refdb_h__
+#define INCLUDE_sys_git_refdb_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * @file git2/sys/refs.h
+ * @brief Low-level Git ref creation
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new direct reference from an OID.
+ *
+ * @param name the reference name
+ * @param oid the object id for a direct reference
+ * @param peel the first non-tag object's OID, or NULL
+ * @return the created git_reference or NULL on error
+ */
+GIT_EXTERN(git_reference *) git_reference__alloc(
+ const char *name,
+ const git_oid *oid,
+ const git_oid *peel);
+
+/**
+ * Create a new symbolic reference.
+ *
+ * @param name the reference name
+ * @param target the target for a symbolic reference
+ * @return the created git_reference or NULL on error
+ */
+GIT_EXTERN(git_reference *) git_reference__alloc_symbolic(
+ const char *name,
+ const char *target);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_sys_git_transport_h
+#define INCLUDE_sys_git_transport_h
+
+#include "git2/net.h"
+#include "git2/types.h"
+
+GIT_BEGIN_DECL
+
+GIT_END_DECL
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_repository_h__
+#define INCLUDE_sys_git_repository_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+
+/**
+ * @file git2/sys/repository.h
+ * @brief Git repository custom implementation routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new repository with neither backends nor config object
+ *
+ * Note that this is only useful if you wish to associate the repository
+ * with a non-filesystem-backed object database and config store.
+ *
+ * @param out The blank repository
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_new(git_repository **out);
+
+/**
+ * Reset all the internal state in a repository.
+ *
+ * This will free all the mapped memory and internal objects
+ * of the repository and leave it in a "blank" state.
+ *
+ * There's no need to call this function directly unless you're
+ * trying to aggressively cleanup the repo before its
+ * deallocation. `git_repository_free` already performs this operation
+ * before deallocation the repo.
+ */
+GIT_EXTERN(void) git_repository__cleanup(git_repository *repo);
+
+/**
+ * Update the filesystem config settings for an open repository
+ *
+ * When a repository is initialized, config values are set based on the
+ * properties of the filesystem that the repository is on, such as
+ * "core.ignorecase", "core.filemode", "core.symlinks", etc. If the
+ * repository is moved to a new filesystem, these properties may no
+ * longer be correct and API calls may not behave as expected. This
+ * call reruns the phase of repository initialization that sets those
+ * properties to compensate for the current filesystem of the repo.
+ *
+ * @param repo A repository object
+ * @param recurse_submodules Should submodules be updated recursively
+ * @return 0 on success, < 0 on error
+ */
+GIT_EXTERN(int) git_repository_reinit_filesystem(
+ git_repository *repo,
+ int recurse_submodules);
+
+/**
+ * Set the configuration file for this repository
+ *
+ * This configuration file will be used for all configuration
+ * queries involving this repository.
+ *
+ * The repository will keep a reference to the config file;
+ * the user must still free the config after setting it
+ * to the repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param config A Config object
+ */
+GIT_EXTERN(void) git_repository_set_config(git_repository *repo, git_config *config);
+
+/**
+ * Set the Object Database for this repository
+ *
+ * The ODB will be used for all object-related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the ODB; the user
+ * must still free the ODB object after setting it to the
+ * repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param odb An ODB object
+ */
+GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb);
+
+/**
+ * Set the Reference Database Backend for this repository
+ *
+ * The refdb will be used for all reference related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the refdb; the user
+ * must still free the refdb object after setting it to the
+ * repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param refdb An refdb object
+ */
+GIT_EXTERN(void) git_repository_set_refdb(git_repository *repo, git_refdb *refdb);
+
+/**
+ * Set the index file for this repository
+ *
+ * This index will be used for all index-related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the index file;
+ * the user must still free the index after setting it
+ * to the repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param index An index object
+ */
+GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index);
+
+/**
+ * Set a repository to be bare.
+ *
+ * Clear the working directory and set core.bare to true. You may also
+ * want to call `git_repository_set_index(repo, NULL)` since a bare repo
+ * typically does not have an index, but this function will not do that
+ * for you.
+ *
+ * @param repo Repo to make bare
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_repository_set_bare(git_repository *repo);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_stream_h__
+#define INCLUDE_sys_git_stream_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/proxy.h"
+
+GIT_BEGIN_DECL
+
+#define GIT_STREAM_VERSION 1
+
+/**
+ * Every stream must have this struct as its first element, so the
+ * API can talk to it. You'd define your stream as
+ *
+ * struct my_stream {
+ * git_stream parent;
+ * ...
+ * }
+ *
+ * and fill the functions
+ */
+typedef struct git_stream {
+ int version;
+
+ int encrypted;
+ int proxy_support;
+ int (*connect)(struct git_stream *);
+ int (*certificate)(git_cert **, struct git_stream *);
+ int (*set_proxy)(struct git_stream *, const git_proxy_options *proxy_opts);
+ ssize_t (*read)(struct git_stream *, void *, size_t);
+ ssize_t (*write)(struct git_stream *, const char *, size_t, int);
+ int (*close)(struct git_stream *);
+ void (*free)(struct git_stream *);
+} git_stream;
+
+typedef int (*git_stream_cb)(git_stream **out, const char *host, const char *port);
+
+/**
+ * Register a TLS stream constructor for the library to use
+ *
+ * If a constructor is already set, it will be overwritten. Pass
+ * `NULL` in order to deregister the current constructor.
+ *
+ * @param ctor the constructor to use
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_stream_register_tls(git_stream_cb ctor);
+
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_time_h__
+#define INCLUDE_git_time_h__
+
+#include "git2/common.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Return a monotonic time value, useful for measuring running time
+ * and setting up timeouts.
+ *
+ * The returned value is an arbitrary point in time -- it can only be
+ * used when comparing it to another `git_time_monotonic` call.
+ *
+ * The time is returned in seconds, with a decimal fraction that differs
+ * on accuracy based on the underlying system, but should be least
+ * accurate to Nanoseconds.
+ *
+ * This function cannot fail.
+ */
+GIT_EXTERN(double) git_time_monotonic(void);
+
+GIT_END_DECL
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_sys_git_transport_h
+#define INCLUDE_sys_git_transport_h
+
+#include "git2/net.h"
+#include "git2/types.h"
+#include "git2/strarray.h"
+#include "git2/proxy.h"
+
+/**
+ * @file git2/sys/transport.h
+ * @brief Git custom transport registration interfaces and functions
+ * @defgroup git_transport Git custom transport registration
+ * @ingroup Git
+ * @{
+ */
+
+GIT_BEGIN_DECL
+
+/**
+ * Flags to pass to transport
+ *
+ * Currently unused.
+ */
+typedef enum {
+ GIT_TRANSPORTFLAGS_NONE = 0,
+} git_transport_flags_t;
+
+struct git_transport {
+ unsigned int version;
+ /* Set progress and error callbacks */
+ int (*set_callbacks)(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
+ void *payload);
+
+ /* Set custom headers for HTTP requests */
+ int (*set_custom_headers)(
+ git_transport *transport,
+ const git_strarray *custom_headers);
+
+ /* Connect the transport to the remote repository, using the given
+ * direction. */
+ int (*connect)(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ const git_proxy_options *proxy_opts,
+ int direction,
+ int flags);
+
+ /* This function may be called after a successful call to
+ * connect(). The array returned is owned by the transport and
+ * is guaranteed until the next call of a transport function. */
+ int (*ls)(
+ const git_remote_head ***out,
+ size_t *size,
+ git_transport *transport);
+
+ /* Executes the push whose context is in the git_push object. */
+ int(*push)(git_transport *transport, git_push *push, const git_remote_callbacks *callbacks);
+
+ /* This function may be called after a successful call to connect(), when
+ * the direction is FETCH. The function performs a negotiation to calculate
+ * the wants list for the fetch. */
+ int (*negotiate_fetch)(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count);
+
+ /* This function may be called after a successful call to negotiate_fetch(),
+ * when the direction is FETCH. This function retrieves the pack file for
+ * the fetch from the remote end. */
+ int (*download_pack)(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload);
+
+ /* Checks to see if the transport is connected */
+ int (*is_connected)(git_transport *transport);
+
+ /* Reads the flags value previously passed into connect() */
+ int (*read_flags)(git_transport *transport, int *flags);
+
+ /* Cancels any outstanding transport operation */
+ void (*cancel)(git_transport *transport);
+
+ /* This function is the reverse of connect() -- it terminates the
+ * connection to the remote end. */
+ int (*close)(git_transport *transport);
+
+ /* Frees/destructs the git_transport object. */
+ void (*free)(git_transport *transport);
+};
+
+#define GIT_TRANSPORT_VERSION 1
+#define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION}
+
+/**
+ * Initializes a `git_transport` with default values. Equivalent to
+ * creating an instance with GIT_TRANSPORT_INIT.
+ *
+ * @param opts the `git_transport` struct to initialize
+ * @param version Version of struct; pass `GIT_TRANSPORT_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_transport_init(
+ git_transport *opts,
+ unsigned int version);
+
+/**
+ * Function to use to create a transport from a URL. The transport database
+ * is scanned to find a transport that implements the scheme of the URI (i.e.
+ * git:// or http://) and a transport object is returned to the caller.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param url The URL to connect to
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);
+
+/**
+ * Create an ssh transport with custom git command paths
+ *
+ * This is a factory function suitable for setting as the transport
+ * callback in a remote (or for a clone in the options).
+ *
+ * The payload argument must be a strarray pointer with the paths for
+ * the `git-upload-pack` and `git-receive-pack` at index 0 and 1.
+ *
+ * @param out the resulting transport
+ * @param owner the owning remote
+ * @param payload a strarray with the paths
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload);
+
+/**
+ * Add a custom transport definition, to be used in addition to the built-in
+ * set of transports that come with libgit2.
+ *
+ * The caller is responsible for synchronizing calls to git_transport_register
+ * and git_transport_unregister with other calls to the library that
+ * instantiate transports.
+ *
+ * @param prefix The scheme (ending in "://") to match, i.e. "git://"
+ * @param cb The callback used to create an instance of the transport
+ * @param param A fixed parameter to pass to cb at creation time
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_register(
+ const char *prefix,
+ git_transport_cb cb,
+ void *param);
+
+/**
+ *
+ * Unregister a custom transport definition which was previously registered
+ * with git_transport_register.
+ *
+ * @param prefix From the previous call to git_transport_register
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_unregister(
+ const char *prefix);
+
+/* Transports which come with libgit2 (match git_transport_cb). The expected
+ * value for "param" is listed in-line below. */
+
+/**
+ * Create an instance of the dummy transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_dummy(
+ git_transport **out,
+ git_remote *owner,
+ /* NULL */ void *payload);
+
+/**
+ * Create an instance of the local transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_local(
+ git_transport **out,
+ git_remote *owner,
+ /* NULL */ void *payload);
+
+/**
+ * Create an instance of the smart transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload A pointer to a git_smart_subtransport_definition
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_smart(
+ git_transport **out,
+ git_remote *owner,
+ /* (git_smart_subtransport_definition *) */ void *payload);
+
+/**
+ * Call the certificate check for this transport.
+ *
+ * @param transport a smart transport
+ * @param cert the certificate to pass to the caller
+ * @param valid whether we believe the certificate is valid
+ * @param hostname the hostname we connected to
+ * @return the return value of the callback
+ */
+GIT_EXTERN(int) git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname);
+
+/**
+ * Call the credentials callback for this transport
+ *
+ * @param out the pointer where the creds are to be stored
+ * @param transport a smart transport
+ * @param user the user we saw on the url (if any)
+ * @param methods available methods for authentication
+ * @return the return value of the callback
+ */
+GIT_EXTERN(int) git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods);
+
+/*
+ *** End of base transport interface ***
+ *** Begin interface for subtransports for the smart transport ***
+ */
+
+/* The smart transport knows how to speak the git protocol, but it has no
+ * knowledge of how to establish a connection between it and another endpoint,
+ * or how to move data back and forth. For this, a subtransport interface is
+ * declared, and the smart transport delegates this work to the subtransports.
+ * Three subtransports are implemented: git, http, and winhttp. (The http and
+ * winhttp transports each implement both http and https.) */
+
+/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1
+ * (request/response). The smart transport handles the differences in its own
+ * logic. The git subtransport is RPC = 0, while http and winhttp are both
+ * RPC = 1. */
+
+/* Actions that the smart transport can ask
+ * a subtransport to perform */
+typedef enum {
+ GIT_SERVICE_UPLOADPACK_LS = 1,
+ GIT_SERVICE_UPLOADPACK = 2,
+ GIT_SERVICE_RECEIVEPACK_LS = 3,
+ GIT_SERVICE_RECEIVEPACK = 4,
+} git_smart_service_t;
+
+typedef struct git_smart_subtransport git_smart_subtransport;
+typedef struct git_smart_subtransport_stream git_smart_subtransport_stream;
+
+/* A stream used by the smart transport to read and write data
+ * from a subtransport */
+struct git_smart_subtransport_stream {
+ /* The owning subtransport */
+ git_smart_subtransport *subtransport;
+
+ int (*read)(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read);
+
+ int (*write)(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len);
+
+ void (*free)(
+ git_smart_subtransport_stream *stream);
+};
+
+/* An implementation of a subtransport which carries data for the
+ * smart transport */
+struct git_smart_subtransport {
+ int (* action)(
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *transport,
+ const char *url,
+ git_smart_service_t action);
+
+ /* Subtransports are guaranteed a call to close() between
+ * calls to action(), except for the following two "natural" progressions
+ * of actions against a constant URL.
+ *
+ * 1. UPLOADPACK_LS -> UPLOADPACK
+ * 2. RECEIVEPACK_LS -> RECEIVEPACK */
+ int (*close)(git_smart_subtransport *transport);
+
+ void (*free)(git_smart_subtransport *transport);
+};
+
+/* A function which creates a new subtransport for the smart transport */
+typedef int (*git_smart_subtransport_cb)(
+ git_smart_subtransport **out,
+ git_transport* owner,
+ void* param);
+
+/**
+ * Definition for a "subtransport"
+ *
+ * This is used to let the smart protocol code know about the protocol
+ * which you are implementing.
+ */
+typedef struct git_smart_subtransport_definition {
+ /** The function to use to create the git_smart_subtransport */
+ git_smart_subtransport_cb callback;
+
+ /**
+ * True if the protocol is stateless; false otherwise. For example,
+ * http:// is stateless, but git:// is not.
+ */
+ unsigned rpc;
+
+ /** Param of the callback
+ */
+ void* param;
+} git_smart_subtransport_definition;
+
+/* Smart transport subtransports that come with libgit2 */
+
+/**
+ * Create an instance of the http subtransport. This subtransport
+ * also supports https. On Win32, this subtransport may be implemented
+ * using the WinHTTP library.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_http(
+ git_smart_subtransport **out,
+ git_transport* owner,
+ void *param);
+
+/**
+ * Create an instance of the git subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_git(
+ git_smart_subtransport **out,
+ git_transport* owner,
+ void *param);
+
+/**
+ * Create an instance of the ssh subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_ssh(
+ git_smart_subtransport **out,
+ git_transport* owner,
+ void *param);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_tag_h__
+#define INCLUDE_git_tag_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "object.h"
+#include "strarray.h"
+
+/**
+ * @file git2/tag.h
+ * @brief Git tag parsing routines
+ * @defgroup git_tag Git tag management
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a tag object from the repository.
+ *
+ * @param out pointer to the looked up tag
+ * @param repo the repo to use when locating the tag.
+ * @param id identity of the tag to locate.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_lookup(
+ git_tag **out, git_repository *repo, const git_oid *id);
+
+/**
+ * Lookup a tag object from the repository,
+ * given a prefix of its identifier (short id).
+ *
+ * @see git_object_lookup_prefix
+ *
+ * @param out pointer to the looked up tag
+ * @param repo the repo to use when locating the tag.
+ * @param id identity of the tag to locate.
+ * @param len the length of the short identifier
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_lookup_prefix(
+ git_tag **out, git_repository *repo, const git_oid *id, size_t len);
+
+/**
+ * Close an open tag
+ *
+ * You can no longer use the git_tag pointer after this call.
+ *
+ * IMPORTANT: You MUST call this method when you are through with a tag to
+ * release memory. Failure to do so will cause a memory leak.
+ *
+ * @param tag the tag to close
+ */
+GIT_EXTERN(void) git_tag_free(git_tag *tag);
+
+/**
+ * Get the id of a tag.
+ *
+ * @param tag a previously loaded tag.
+ * @return object identity for the tag.
+ */
+GIT_EXTERN(const git_oid *) git_tag_id(const git_tag *tag);
+
+/**
+ * Get the repository that contains the tag.
+ *
+ * @param tag A previously loaded tag.
+ * @return Repository that contains this tag.
+ */
+GIT_EXTERN(git_repository *) git_tag_owner(const git_tag *tag);
+
+/**
+ * Get the tagged object of a tag
+ *
+ * This method performs a repository lookup for the
+ * given object and returns it
+ *
+ * @param target_out pointer where to store the target
+ * @param tag a previously loaded tag.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_target(git_object **target_out, const git_tag *tag);
+
+/**
+ * Get the OID of the tagged object of a tag
+ *
+ * @param tag a previously loaded tag.
+ * @return pointer to the OID
+ */
+GIT_EXTERN(const git_oid *) git_tag_target_id(const git_tag *tag);
+
+/**
+ * Get the type of a tag's tagged object
+ *
+ * @param tag a previously loaded tag.
+ * @return type of the tagged object
+ */
+GIT_EXTERN(git_otype) git_tag_target_type(const git_tag *tag);
+
+/**
+ * Get the name of a tag
+ *
+ * @param tag a previously loaded tag.
+ * @return name of the tag
+ */
+GIT_EXTERN(const char *) git_tag_name(const git_tag *tag);
+
+/**
+ * Get the tagger (author) of a tag
+ *
+ * @param tag a previously loaded tag.
+ * @return reference to the tag's author or NULL when unspecified
+ */
+GIT_EXTERN(const git_signature *) git_tag_tagger(const git_tag *tag);
+
+/**
+ * Get the message of a tag
+ *
+ * @param tag a previously loaded tag.
+ * @return message of the tag or NULL when unspecified
+ */
+GIT_EXTERN(const char *) git_tag_message(const git_tag *tag);
+
+
+/**
+ * Create a new tag in the repository from an object
+ *
+ * A new reference will also be created pointing to
+ * this tag object. If `force` is true and a reference
+ * already exists with the given name, it'll be replaced.
+ *
+ * The message will not be cleaned up. This can be achieved
+ * through `git_message_prettify()`.
+ *
+ * The tag name will be checked for validity. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * @param oid Pointer where to store the OID of the
+ * newly created tag. If the tag already exists, this parameter
+ * will be the oid of the existing tag, and the function will
+ * return a GIT_EEXISTS error code.
+ *
+ * @param repo Repository where to store the tag
+ *
+ * @param tag_name Name for the tag; this name is validated
+ * for consistency. It should also not conflict with an
+ * already existing tag name
+ *
+ * @param target Object to which this tag points. This object
+ * must belong to the given `repo`.
+ *
+ * @param tagger Signature of the tagger for this tag, and
+ * of the tagging time
+ *
+ * @param message Full message for this tag
+ *
+ * @param force Overwrite existing references
+ *
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
+ * A tag object is written to the ODB, and a proper reference
+ * is written in the /refs/tags folder, pointing to it
+ */
+GIT_EXTERN(int) git_tag_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int force);
+
+/**
+ * Create a new tag in the object database pointing to a git_object
+ *
+ * The message will not be cleaned up. This can be achieved
+ * through `git_message_prettify()`.
+ *
+ * @param oid Pointer where to store the OID of the
+ * newly created tag
+ *
+ * @param repo Repository where to store the tag
+ *
+ * @param tag_name Name for the tag
+ *
+ * @param target Object to which this tag points. This object
+ * must belong to the given `repo`.
+ *
+ * @param tagger Signature of the tagger for this tag, and
+ * of the tagging time
+ *
+ * @param message Full message for this tag
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message);
+
+/**
+ * Create a new tag in the repository from a buffer
+ *
+ * @param oid Pointer where to store the OID of the newly created tag
+ * @param repo Repository where to store the tag
+ * @param buffer Raw tag data
+ * @param force Overwrite existing tags
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_tag_create_frombuffer(
+ git_oid *oid,
+ git_repository *repo,
+ const char *buffer,
+ int force);
+
+/**
+ * Create a new lightweight tag pointing at a target object
+ *
+ * A new direct reference will be created pointing to
+ * this target object. If `force` is true and a reference
+ * already exists with the given name, it'll be replaced.
+ *
+ * The tag name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param oid Pointer where to store the OID of the provided
+ * target object. If the tag already exists, this parameter
+ * will be filled with the oid of the existing pointed object
+ * and the function will return a GIT_EEXISTS error code.
+ *
+ * @param repo Repository where to store the lightweight tag
+ *
+ * @param tag_name Name for the tag; this name is validated
+ * for consistency. It should also not conflict with an
+ * already existing tag name
+ *
+ * @param target Object to which this tag points. This object
+ * must belong to the given `repo`.
+ *
+ * @param force Overwrite existing references
+ *
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
+ * A proper reference is written in the /refs/tags folder,
+ * pointing to the provided target object
+ */
+GIT_EXTERN(int) git_tag_create_lightweight(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ int force);
+
+/**
+ * Delete an existing tag reference.
+ *
+ * The tag name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param repo Repository where lives the tag
+ *
+ * @param tag_name Name of the tag to be deleted;
+ * this name is validated for consistency.
+ *
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_tag_delete(
+ git_repository *repo,
+ const char *tag_name);
+
+/**
+ * Fill a list with all the tags in the Repository
+ *
+ * The string array will be filled with the names of the
+ * matching tags; these values are owned by the user and
+ * should be free'd manually when no longer needed, using
+ * `git_strarray_free`.
+ *
+ * @param tag_names Pointer to a git_strarray structure where
+ * the tag names will be stored
+ * @param repo Repository where to find the tags
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_list(
+ git_strarray *tag_names,
+ git_repository *repo);
+
+/**
+ * Fill a list with all the tags in the Repository
+ * which name match a defined pattern
+ *
+ * If an empty pattern is provided, all the tags
+ * will be returned.
+ *
+ * The string array will be filled with the names of the
+ * matching tags; these values are owned by the user and
+ * should be free'd manually when no longer needed, using
+ * `git_strarray_free`.
+ *
+ * @param tag_names Pointer to a git_strarray structure where
+ * the tag names will be stored
+ * @param pattern Standard fnmatch pattern
+ * @param repo Repository where to find the tags
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_list_match(
+ git_strarray *tag_names,
+ const char *pattern,
+ git_repository *repo);
+
+
+typedef int (*git_tag_foreach_cb)(const char *name, git_oid *oid, void *payload);
+
+/**
+ * Call callback `cb' for each tag in the repository
+ *
+ * @param repo Repository
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ */
+GIT_EXTERN(int) git_tag_foreach(
+ git_repository *repo,
+ git_tag_foreach_cb callback,
+ void *payload);
+
+
+/**
+ * Recursively peel a tag until a non tag git_object is found
+ *
+ * The retrieved `tag_target` object is owned by the repository
+ * and should be closed with the `git_object_free` method.
+ *
+ * @param tag_target_out Pointer to the peeled git_object
+ * @param tag The tag to be processed
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_peel(
+ git_object **tag_target_out,
+ const git_tag *tag);
+
+/**
+ * Create an in-memory copy of a tag. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the tag
+ * @param source Original tag to copy
+ */
+GIT_EXTERN(int) git_tag_dup(git_tag **out, git_tag *source);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_trace_h__
+#define INCLUDE_git_trace_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/trace.h
+ * @brief Git tracing configuration routines
+ * @defgroup git_trace Git tracing configuration routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Available tracing levels. When tracing is set to a particular level,
+ * callers will be provided tracing at the given level and all lower levels.
+ */
+typedef enum {
+ /** No tracing will be performed. */
+ GIT_TRACE_NONE = 0,
+
+ /** Severe errors that may impact the program's execution */
+ GIT_TRACE_FATAL = 1,
+
+ /** Errors that do not impact the program's execution */
+ GIT_TRACE_ERROR = 2,
+
+ /** Warnings that suggest abnormal data */
+ GIT_TRACE_WARN = 3,
+
+ /** Informational messages about program execution */
+ GIT_TRACE_INFO = 4,
+
+ /** Detailed data that allows for debugging */
+ GIT_TRACE_DEBUG = 5,
+
+ /** Exceptionally detailed debugging data */
+ GIT_TRACE_TRACE = 6
+} git_trace_level_t;
+
+/**
+ * An instance for a tracing function
+ */
+typedef void (*git_trace_callback)(git_trace_level_t level, const char *msg);
+
+/**
+ * Sets the system tracing configuration to the specified level with the
+ * specified callback. When system events occur at a level equal to, or
+ * lower than, the given level they will be reported to the given callback.
+ *
+ * @param level Level to set tracing to
+ * @param cb Function to call with trace data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_callback cb);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_transaction_h__
+#define INCLUDE_git_transaction_h__
+
+#include "common.h"
+
+/**
+ * @file git2/transaction.h
+ * @brief Git transactional reference routines
+ * @defgroup git_transaction Git transactional reference routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new transaction object
+ *
+ * This does not lock anything, but sets up the transaction object to
+ * know from which repository to lock.
+ *
+ * @param out the resulting transaction
+ * @param repo the repository in which to lock
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transaction_new(git_transaction **out, git_repository *repo);
+
+/**
+ * Lock a reference
+ *
+ * Lock the specified reference. This is the first step to updating a
+ * reference.
+ *
+ * @param tx the transaction
+ * @param refname the reference to lock
+ * @return 0 or an error message
+ */
+GIT_EXTERN(int) git_transaction_lock_ref(git_transaction *tx, const char *refname);
+
+/**
+ * Set the target of a reference
+ *
+ * Set the target of the specified reference. This reference must be
+ * locked.
+ *
+ * @param tx the transaction
+ * @param refname reference to update
+ * @param target target to set the reference to
+ * @param sig signature to use in the reflog; pass NULL to read the identity from the config
+ * @param msg message to use in the reflog
+ * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
+ */
+GIT_EXTERN(int) git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg);
+
+/**
+ * Set the target of a reference
+ *
+ * Set the target of the specified reference. This reference must be
+ * locked.
+ *
+ * @param tx the transaction
+ * @param refname reference to update
+ * @param target target to set the reference to
+ * @param sig signature to use in the reflog; pass NULL to read the identity from the config
+ * @param msg message to use in the reflog
+ * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
+ */
+GIT_EXTERN(int) git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg);
+
+/**
+ * Set the reflog of a reference
+ *
+ * Set the specified reference's reflog. If this is combined with
+ * setting the target, that update won't be written to the reflog.
+ *
+ * @param tx the transaction
+ * @param refname the reference whose reflog to set
+ * @param reflog the reflog as it should be written out
+ * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
+ */
+GIT_EXTERN(int) git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog);
+
+/**
+ * Remove a reference
+ *
+ * @param tx the transaction
+ * @param refname the reference to remove
+ * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
+ */
+GIT_EXTERN(int) git_transaction_remove(git_transaction *tx, const char *refname);
+
+/**
+ * Commit the changes from the transaction
+ *
+ * Perform the changes that have been queued. The updates will be made
+ * one by one, and the first failure will stop the processing.
+ *
+ * @param tx the transaction
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transaction_commit(git_transaction *tx);
+
+/**
+ * Free the resources allocated by this transaction
+ *
+ * If any references remain locked, they will be unlocked without any
+ * changes made to them.
+ *
+ * @param tx the transaction
+ */
+GIT_EXTERN(void) git_transaction_free(git_transaction *tx);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_transport_h__
+#define INCLUDE_git_transport_h__
+
+#include "indexer.h"
+#include "net.h"
+#include "types.h"
+
+/**
+ * @file git2/transport.h
+ * @brief Git transport interfaces and functions
+ * @defgroup git_transport interfaces and functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Signature of a function which creates a transport */
+typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);
+
+/**
+ * Type of SSH host fingerprint
+ */
+typedef enum {
+ /** MD5 is available */
+ GIT_CERT_SSH_MD5 = (1 << 0),
+ /** SHA-1 is available */
+ GIT_CERT_SSH_SHA1 = (1 << 1),
+} git_cert_ssh_t;
+
+/**
+ * Hostkey information taken from libssh2
+ */
+typedef struct {
+ git_cert parent;
+
+ /**
+ * A hostkey type from libssh2, either
+ * `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1`
+ */
+ git_cert_ssh_t type;
+
+ /**
+ * Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will
+ * have the MD5 hash of the hostkey.
+ */
+ unsigned char hash_md5[16];
+
+ /**
+ * Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will
+ * have the SHA-1 hash of the hostkey.
+ */
+ unsigned char hash_sha1[20];
+} git_cert_hostkey;
+
+/**
+ * X.509 certificate information
+ */
+typedef struct {
+ git_cert parent;
+ /**
+ * Pointer to the X.509 certificate data
+ */
+ void *data;
+ /**
+ * Length of the memory block pointed to by `data`.
+ */
+ size_t len;
+} git_cert_x509;
+
+/*
+ *** Begin interface for credentials acquisition ***
+ */
+
+/** Authentication type requested */
+typedef enum {
+ /* git_cred_userpass_plaintext */
+ GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0),
+
+ /* git_cred_ssh_key */
+ GIT_CREDTYPE_SSH_KEY = (1u << 1),
+
+ /* git_cred_ssh_custom */
+ GIT_CREDTYPE_SSH_CUSTOM = (1u << 2),
+
+ /* git_cred_default */
+ GIT_CREDTYPE_DEFAULT = (1u << 3),
+
+ /* git_cred_ssh_interactive */
+ GIT_CREDTYPE_SSH_INTERACTIVE = (1u << 4),
+
+ /**
+ * Username-only information
+ *
+ * If the SSH transport does not know which username to use,
+ * it will ask via this credential type.
+ */
+ GIT_CREDTYPE_USERNAME = (1u << 5),
+
+ /**
+ * Credentials read from memory.
+ *
+ * Only available for libssh2+OpenSSL for now.
+ */
+ GIT_CREDTYPE_SSH_MEMORY = (1u << 6),
+} git_credtype_t;
+
+/* The base structure for all credential types */
+typedef struct git_cred git_cred;
+
+struct git_cred {
+ git_credtype_t credtype;
+ void (*free)(git_cred *cred);
+};
+
+/** A plaintext username and password */
+typedef struct {
+ git_cred parent;
+ char *username;
+ char *password;
+} git_cred_userpass_plaintext;
+
+
+/*
+ * If the user hasn't included libssh2.h before git2.h, we need to
+ * define a few types for the callback signatures.
+ */
+#ifndef LIBSSH2_VERSION
+typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION;
+typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT LIBSSH2_USERAUTH_KBDINT_PROMPT;
+typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE LIBSSH2_USERAUTH_KBDINT_RESPONSE;
+#endif
+
+typedef int (*git_cred_sign_callback)(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract);
+typedef void (*git_cred_ssh_interactive_callback)(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract);
+
+/**
+ * A ssh key from disk
+ */
+typedef struct git_cred_ssh_key {
+ git_cred parent;
+ char *username;
+ char *publickey;
+ char *privatekey;
+ char *passphrase;
+} git_cred_ssh_key;
+
+/**
+ * Keyboard-interactive based ssh authentication
+ */
+typedef struct git_cred_ssh_interactive {
+ git_cred parent;
+ char *username;
+ git_cred_ssh_interactive_callback prompt_callback;
+ void *payload;
+} git_cred_ssh_interactive;
+
+/**
+ * A key with a custom signature function
+ */
+typedef struct git_cred_ssh_custom {
+ git_cred parent;
+ char *username;
+ char *publickey;
+ size_t publickey_len;
+ git_cred_sign_callback sign_callback;
+ void *payload;
+} git_cred_ssh_custom;
+
+/** A key for NTLM/Kerberos "default" credentials */
+typedef struct git_cred git_cred_default;
+
+/** Username-only credential information */
+typedef struct git_cred_username {
+ git_cred parent;
+ char username[1];
+} git_cred_username;
+
+/**
+ * Check whether a credential object contains username information.
+ *
+ * @param cred object to check
+ * @return 1 if the credential object has non-NULL username, 0 otherwise
+ */
+GIT_EXTERN(int) git_cred_has_username(git_cred *cred);
+
+/**
+ * Create a new plain-text username and password credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username The username of the credential.
+ * @param password The password of the credential.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_userpass_plaintext_new(
+ git_cred **out,
+ const char *username,
+ const char *password);
+
+/**
+ * Create a new passphrase-protected ssh key credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @param publickey The path to the public key of the credential.
+ * @param privatekey The path to the private key of the credential.
+ * @param passphrase The passphrase of the credential.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_key_new(
+ git_cred **out,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase);
+
+/**
+ * Create a new ssh keyboard-interactive based credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param username Username to use to authenticate.
+ * @param prompt_callback The callback method used for prompts.
+ * @param payload Additional data to pass to the callback.
+ * @return 0 for success or an error code for failure.
+ */
+GIT_EXTERN(int) git_cred_ssh_interactive_new(
+ git_cred **out,
+ const char *username,
+ git_cred_ssh_interactive_callback prompt_callback,
+ void *payload);
+
+/**
+ * Create a new ssh key credential object used for querying an ssh-agent.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_key_from_agent(
+ git_cred **out,
+ const char *username);
+
+/**
+ * Create an ssh key credential with a custom signing function.
+ *
+ * This lets you use your own function to sign the challenge.
+ *
+ * This function and its credential type is provided for completeness
+ * and wraps `libssh2_userauth_publickey()`, which is undocumented.
+ *
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @param publickey The bytes of the public key.
+ * @param publickey_len The length of the public key in bytes.
+ * @param sign_callback The callback method to sign the data during the challenge.
+ * @param payload Additional data to pass to the callback.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_custom_new(
+ git_cred **out,
+ const char *username,
+ const char *publickey,
+ size_t publickey_len,
+ git_cred_sign_callback sign_callback,
+ void *payload);
+
+/**
+ * Create a "default" credential usable for Negotiate mechanisms like NTLM
+ * or Kerberos authentication.
+ *
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_default_new(git_cred **out);
+
+/**
+ * Create a credential to specify a username.
+ *
+ * This is used with ssh authentication to query for the username if
+ * none is specified in the url.
+ */
+GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username);
+
+/**
+ * Create a new ssh key credential object reading the keys from memory.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate.
+ * @param publickey The public key of the credential.
+ * @param privatekey The private key of the credential.
+ * @param passphrase The passphrase of the credential.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_key_memory_new(
+ git_cred **out,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase);
+
+
+/**
+ * Free a credential.
+ *
+ * This is only necessary if you own the object; that is, if you are a
+ * transport.
+ *
+ * @param cred the object to free
+ */
+GIT_EXTERN(void) git_cred_free(git_cred *cred);
+
+/**
+ * Signature of a function which acquires a credential object.
+ *
+ * - cred: The newly created credential object.
+ * - url: The resource for which we are demanding a credential.
+ * - username_from_url: The username that was embedded in a "user\@host"
+ * remote url, or NULL if not included.
+ * - allowed_types: A bitmask stating which cred types are OK to return.
+ * - payload: The payload provided when specifying this callback.
+ * - returns 0 for success, < 0 to indicate an error, > 0 to indicate
+ * no credential was acquired
+ */
+typedef int (*git_cred_acquire_cb)(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *payload);
+
+/** @} */
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_tree_h__
+#define INCLUDE_git_tree_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "object.h"
+
+/**
+ * @file git2/tree.h
+ * @brief Git tree parsing, loading routines
+ * @defgroup git_tree Git tree parsing, loading routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Lookup a tree object from the repository.
+ *
+ * @param out Pointer to the looked up tree
+ * @param repo The repo to use when locating the tree.
+ * @param id Identity of the tree to locate.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_lookup(
+ git_tree **out, git_repository *repo, const git_oid *id);
+
+/**
+ * Lookup a tree object from the repository,
+ * given a prefix of its identifier (short id).
+ *
+ * @see git_object_lookup_prefix
+ *
+ * @param out pointer to the looked up tree
+ * @param repo the repo to use when locating the tree.
+ * @param id identity of the tree to locate.
+ * @param len the length of the short identifier
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_lookup_prefix(
+ git_tree **out,
+ git_repository *repo,
+ const git_oid *id,
+ size_t len);
+
+/**
+ * Close an open tree
+ *
+ * You can no longer use the git_tree pointer after this call.
+ *
+ * IMPORTANT: You MUST call this method when you stop using a tree to
+ * release memory. Failure to do so will cause a memory leak.
+ *
+ * @param tree The tree to close
+ */
+GIT_EXTERN(void) git_tree_free(git_tree *tree);
+
+/**
+ * Get the id of a tree.
+ *
+ * @param tree a previously loaded tree.
+ * @return object identity for the tree.
+ */
+GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree);
+
+/**
+ * Get the repository that contains the tree.
+ *
+ * @param tree A previously loaded tree.
+ * @return Repository that contains this tree.
+ */
+GIT_EXTERN(git_repository *) git_tree_owner(const git_tree *tree);
+
+/**
+ * Get the number of entries listed in a tree
+ *
+ * @param tree a previously loaded tree.
+ * @return the number of entries in the tree
+ */
+GIT_EXTERN(size_t) git_tree_entrycount(const git_tree *tree);
+
+/**
+ * Lookup a tree entry by its filename
+ *
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
+ * @param tree a previously loaded tree.
+ * @param filename the filename of the desired entry
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(
+ const git_tree *tree, const char *filename);
+
+/**
+ * Lookup a tree entry by its position in the tree
+ *
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
+ * @param tree a previously loaded tree.
+ * @param idx the position in the entry list
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(
+ const git_tree *tree, size_t idx);
+
+/**
+ * Lookup a tree entry by SHA value.
+ *
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
+ * Warning: this must examine every entry in the tree, so it is not fast.
+ *
+ * @param tree a previously loaded tree.
+ * @param id the sha being looked for
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byid(
+ const git_tree *tree, const git_oid *id);
+
+/**
+ * Retrieve a tree entry contained in a tree or in any of its subtrees,
+ * given its relative path.
+ *
+ * Unlike the other lookup functions, the returned tree entry is owned by
+ * the user and must be freed explicitly with `git_tree_entry_free()`.
+ *
+ * @param out Pointer where to store the tree entry
+ * @param root Previously loaded tree which is the root of the relative path
+ * @param path Path to the contained entry
+ * @return 0 on success; GIT_ENOTFOUND if the path does not exist
+ */
+GIT_EXTERN(int) git_tree_entry_bypath(
+ git_tree_entry **out,
+ const git_tree *root,
+ const char *path);
+
+/**
+ * Duplicate a tree entry
+ *
+ * Create a copy of a tree entry. The returned copy is owned by the user,
+ * and must be freed explicitly with `git_tree_entry_free()`.
+ *
+ * @param dest pointer where to store the copy
+ * @param source tree entry to duplicate
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source);
+
+/**
+ * Free a user-owned tree entry
+ *
+ * IMPORTANT: This function is only needed for tree entries owned by the
+ * user, such as the ones returned by `git_tree_entry_dup()` or
+ * `git_tree_entry_bypath()`.
+ *
+ * @param entry The entry to free
+ */
+GIT_EXTERN(void) git_tree_entry_free(git_tree_entry *entry);
+
+/**
+ * Get the filename of a tree entry
+ *
+ * @param entry a tree entry
+ * @return the name of the file
+ */
+GIT_EXTERN(const char *) git_tree_entry_name(const git_tree_entry *entry);
+
+/**
+ * Get the id of the object pointed by the entry
+ *
+ * @param entry a tree entry
+ * @return the oid of the object
+ */
+GIT_EXTERN(const git_oid *) git_tree_entry_id(const git_tree_entry *entry);
+
+/**
+ * Get the type of the object pointed by the entry
+ *
+ * @param entry a tree entry
+ * @return the type of the pointed object
+ */
+GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry);
+
+/**
+ * Get the UNIX file attributes of a tree entry
+ *
+ * @param entry a tree entry
+ * @return filemode as an integer
+ */
+GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry);
+
+/**
+ * Get the raw UNIX file attributes of a tree entry
+ *
+ * This function does not perform any normalization and is only useful
+ * if you need to be able to recreate the original tree object.
+ *
+ * @param entry a tree entry
+ * @return filemode as an integer
+ */
+
+GIT_EXTERN(git_filemode_t) git_tree_entry_filemode_raw(const git_tree_entry *entry);
+/**
+ * Compare two tree entries
+ *
+ * @param e1 first tree entry
+ * @param e2 second tree entry
+ * @return <0 if e1 is before e2, 0 if e1 == e2, >0 if e1 is after e2
+ */
+GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2);
+
+/**
+ * Convert a tree entry to the git_object it points to.
+ *
+ * You must call `git_object_free()` on the object when you are done with it.
+ *
+ * @param object_out pointer to the converted object
+ * @param repo repository where to lookup the pointed object
+ * @param entry a tree entry
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_entry_to_object(
+ git_object **object_out,
+ git_repository *repo,
+ const git_tree_entry *entry);
+
+/**
+ * Create a new tree builder.
+ *
+ * The tree builder can be used to create or modify trees in memory and
+ * write them as tree objects to the database.
+ *
+ * If the `source` parameter is not NULL, the tree builder will be
+ * initialized with the entries of the given tree.
+ *
+ * If the `source` parameter is NULL, the tree builder will start with no
+ * entries and will have to be filled manually.
+ *
+ * @param out Pointer where to store the tree builder
+ * @param repo Repository in which to store the object
+ * @param source Source tree to initialize the builder (optional)
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_treebuilder_new(
+ git_treebuilder **out, git_repository *repo, const git_tree *source);
+
+/**
+ * Clear all the entires in the builder
+ *
+ * @param bld Builder to clear
+ */
+GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld);
+
+/**
+ * Get the number of entries listed in a treebuilder
+ *
+ * @param bld a previously loaded treebuilder.
+ * @return the number of entries in the treebuilder
+ */
+GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld);
+
+/**
+ * Free a tree builder
+ *
+ * This will clear all the entries and free to builder.
+ * Failing to free the builder after you're done using it
+ * will result in a memory leak
+ *
+ * @param bld Builder to free
+ */
+GIT_EXTERN(void) git_treebuilder_free(git_treebuilder *bld);
+
+/**
+ * Get an entry from the builder from its filename
+ *
+ * The returned entry is owned by the builder and should
+ * not be freed manually.
+ *
+ * @param bld Tree builder
+ * @param filename Name of the entry
+ * @return pointer to the entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(
+ git_treebuilder *bld, const char *filename);
+
+/**
+ * Add or update an entry to the builder
+ *
+ * Insert a new entry for `filename` in the builder with the
+ * given attributes.
+ *
+ * If an entry named `filename` already exists, its attributes
+ * will be updated with the given ones.
+ *
+ * The optional pointer `out` can be used to retrieve a pointer to the
+ * newly created/updated entry. Pass NULL if you do not need it. The
+ * pointer may not be valid past the next operation in this
+ * builder. Duplicate the entry if you want to keep it.
+ *
+ * No attempt is being made to ensure that the provided oid points
+ * to an existing git object in the object database, nor that the
+ * attributes make sense regarding the type of the pointed at object.
+ *
+ * @param out Pointer to store the entry (optional)
+ * @param bld Tree builder
+ * @param filename Filename of the entry
+ * @param id SHA1 oid of the entry
+ * @param filemode Folder attributes of the entry. This parameter must
+ * be valued with one of the following entries: 0040000, 0100644,
+ * 0100755, 0120000 or 0160000.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_treebuilder_insert(
+ const git_tree_entry **out,
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode);
+
+/**
+ * Remove an entry from the builder by its filename
+ *
+ * @param bld Tree builder
+ * @param filename Filename of the entry to remove
+ */
+GIT_EXTERN(int) git_treebuilder_remove(
+ git_treebuilder *bld, const char *filename);
+
+/**
+ * Callback for git_treebuilder_filter
+ *
+ * The return value is treated as a boolean, with zero indicating that the
+ * entry should be left alone and any non-zero value meaning that the
+ * entry should be removed from the treebuilder list (i.e. filtered out).
+ */
+typedef int (*git_treebuilder_filter_cb)(
+ const git_tree_entry *entry, void *payload);
+
+/**
+ * Selectively remove entries in the tree
+ *
+ * The `filter` callback will be called for each entry in the tree with a
+ * pointer to the entry and the provided `payload`; if the callback returns
+ * non-zero, the entry will be filtered (removed from the builder).
+ *
+ * @param bld Tree builder
+ * @param filter Callback to filter entries
+ * @param payload Extra data to pass to filter callback
+ */
+GIT_EXTERN(void) git_treebuilder_filter(
+ git_treebuilder *bld,
+ git_treebuilder_filter_cb filter,
+ void *payload);
+
+/**
+ * Write the contents of the tree builder as a tree object
+ *
+ * The tree builder will be written to the given `repo`, and its
+ * identifying SHA1 hash will be stored in the `id` pointer.
+ *
+ * @param id Pointer to store the OID of the newly written tree
+ * @param bld Tree builder to write
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_treebuilder_write(
+ git_oid *id, git_treebuilder *bld);
+
+
+/** Callback for the tree traversal method */
+typedef int (*git_treewalk_cb)(
+ const char *root, const git_tree_entry *entry, void *payload);
+
+/** Tree traversal modes */
+typedef enum {
+ GIT_TREEWALK_PRE = 0, /* Pre-order */
+ GIT_TREEWALK_POST = 1, /* Post-order */
+} git_treewalk_mode;
+
+/**
+ * Traverse the entries in a tree and its subtrees in post or pre order.
+ *
+ * The entries will be traversed in the specified order, children subtrees
+ * will be automatically loaded as required, and the `callback` will be
+ * called once per entry with the current (relative) root for the entry and
+ * the entry data itself.
+ *
+ * If the callback returns a positive value, the passed entry will be
+ * skipped on the traversal (in pre mode). A negative value stops the walk.
+ *
+ * @param tree The tree to walk
+ * @param mode Traversal mode (pre or post-order)
+ * @param callback Function to call on each tree entry
+ * @param payload Opaque pointer to be passed on each callback
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_walk(
+ const git_tree *tree,
+ git_treewalk_mode mode,
+ git_treewalk_cb callback,
+ void *payload);
+
+/**
+ * Create an in-memory copy of a tree. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the tree
+ * @param source Original tree to copy
+ */
+GIT_EXTERN(int) git_tree_dup(git_tree **out, git_tree *source);
+
+/**
+ * The kind of update to perform
+ */
+typedef enum {
+ /** Update or insert an entry at the specified path */
+ GIT_TREE_UPDATE_UPSERT,
+ /** Remove an entry from the specified path */
+ GIT_TREE_UPDATE_REMOVE,
+} git_tree_update_t;
+
+/**
+ * An action to perform during the update of a tree
+ */
+typedef struct {
+ /** Update action. If it's an removal, only the path is looked at */
+ git_tree_update_t action;
+ /** The entry's id */
+ git_oid id;
+ /** The filemode/kind of object */
+ git_filemode_t filemode;
+ /** The full path from the root tree */
+ const char *path;
+} git_tree_update;
+
+/**
+ * Create a tree based on another one with the specified modifications
+ *
+ * Given the `baseline` perform the changes described in the list of
+ * `updates` and create a new tree.
+ *
+ * This function is optimized for common file/directory addition, removal and
+ * replacement in trees. It is much more efficient than reading the tree into a
+ * `git_index` and modifying that, but in exchange it is not as flexible.
+ *
+ * Deleting and adding the same entry is undefined behaviour, changing
+ * a tree to a blob or viceversa is not supported.
+ *
+ * @param out id of the new tree
+ * @param repo the repository in which to create the tree, must be the
+ * same as for `baseline`
+ * @param baseline the tree to base these changes on
+ * @param nupdates the number of elements in the update list
+ * @param updates the list of updates to perform
+ */
+GIT_EXTERN(int) git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates);
+
+/** @} */
+
+GIT_END_DECL
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_types_h__
+#define INCLUDE_git_types_h__
+
+#include "common.h"
+
+/**
+ * @file git2/types.h
+ * @brief libgit2 base & compatibility types
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Cross-platform compatibility types for off_t / time_t
+ *
+ * NOTE: This needs to be in a public header so that both the library
+ * implementation and client applications both agree on the same types.
+ * Otherwise we get undefined behavior.
+ *
+ * Use the "best" types that each platform provides. Currently we truncate
+ * these intermediate representations for compatibility with the git ABI, but
+ * if and when it changes to support 64 bit types, our code will naturally
+ * adapt.
+ * NOTE: These types should match those that are returned by our internal
+ * stat() functions, for all platforms.
+ */
+#include <sys/types.h>
+#ifdef __amigaos4__
+#include <stdint.h>
+#endif
+
+#if defined(_MSC_VER)
+
+typedef __int64 git_off_t;
+typedef __time64_t git_time_t;
+
+#elif defined(__MINGW32__)
+
+typedef off64_t git_off_t;
+typedef __time64_t git_time_t;
+
+#elif defined(__HAIKU__)
+
+typedef __haiku_std_int64 git_off_t;
+typedef __haiku_std_int64 git_time_t;
+
+#else /* POSIX */
+
+/*
+ * Note: Can't use off_t since if a client program includes <sys/types.h>
+ * before us (directly or indirectly), they'll get 32 bit off_t in their client
+ * app, even though /we/ define _FILE_OFFSET_BITS=64.
+ */
+typedef int64_t git_off_t;
+typedef int64_t git_time_t;
+
+#endif
+
+/** Basic type (loose or packed) of any Git object. */
+typedef enum {
+ GIT_OBJ_ANY = -2, /**< Object can be any of the following */
+ GIT_OBJ_BAD = -1, /**< Object is invalid. */
+ GIT_OBJ__EXT1 = 0, /**< Reserved for future use. */
+ GIT_OBJ_COMMIT = 1, /**< A commit object. */
+ GIT_OBJ_TREE = 2, /**< A tree (directory listing) object. */
+ GIT_OBJ_BLOB = 3, /**< A file revision object. */
+ GIT_OBJ_TAG = 4, /**< An annotated tag object. */
+ GIT_OBJ__EXT2 = 5, /**< Reserved for future use. */
+ GIT_OBJ_OFS_DELTA = 6, /**< A delta, base is given by an offset. */
+ GIT_OBJ_REF_DELTA = 7, /**< A delta, base is given by object id. */
+} git_otype;
+
+/** An open object database handle. */
+typedef struct git_odb git_odb;
+
+/** A custom backend in an ODB */
+typedef struct git_odb_backend git_odb_backend;
+
+/** An object read from the ODB */
+typedef struct git_odb_object git_odb_object;
+
+/** A stream to read/write from the ODB */
+typedef struct git_odb_stream git_odb_stream;
+
+/** A stream to write a packfile to the ODB */
+typedef struct git_odb_writepack git_odb_writepack;
+
+/** An open refs database handle. */
+typedef struct git_refdb git_refdb;
+
+/** A custom backend for refs */
+typedef struct git_refdb_backend git_refdb_backend;
+
+/**
+ * Representation of an existing git repository,
+ * including all its object contents
+ */
+typedef struct git_repository git_repository;
+
+/** Representation of a generic object in a repository */
+typedef struct git_object git_object;
+
+/** Representation of an in-progress walk through the commits in a repo */
+typedef struct git_revwalk git_revwalk;
+
+/** Parsed representation of a tag object. */
+typedef struct git_tag git_tag;
+
+/** In-memory representation of a blob object. */
+typedef struct git_blob git_blob;
+
+/** Parsed representation of a commit object. */
+typedef struct git_commit git_commit;
+
+/** Representation of each one of the entries in a tree object. */
+typedef struct git_tree_entry git_tree_entry;
+
+/** Representation of a tree object. */
+typedef struct git_tree git_tree;
+
+/** Constructor for in-memory trees */
+typedef struct git_treebuilder git_treebuilder;
+
+/** Memory representation of an index file. */
+typedef struct git_index git_index;
+
+/** An iterator for conflicts in the index. */
+typedef struct git_index_conflict_iterator git_index_conflict_iterator;
+
+/** Memory representation of a set of config files */
+typedef struct git_config git_config;
+
+/** Interface to access a configuration file */
+typedef struct git_config_backend git_config_backend;
+
+/** Representation of a reference log entry */
+typedef struct git_reflog_entry git_reflog_entry;
+
+/** Representation of a reference log */
+typedef struct git_reflog git_reflog;
+
+/** Representation of a git note */
+typedef struct git_note git_note;
+
+/** Representation of a git packbuilder */
+typedef struct git_packbuilder git_packbuilder;
+
+/** Time in a signature */
+typedef struct git_time {
+ git_time_t time; /**< time in seconds from epoch */
+ int offset; /**< timezone offset, in minutes */
+} git_time;
+
+/** An action signature (e.g. for committers, taggers, etc) */
+typedef struct git_signature {
+ char *name; /**< full name of the author */
+ char *email; /**< email of the author */
+ git_time when; /**< time when the action happened */
+} git_signature;
+
+/** In-memory representation of a reference. */
+typedef struct git_reference git_reference;
+
+/** Iterator for references */
+typedef struct git_reference_iterator git_reference_iterator;
+
+/** Transactional interface to references */
+typedef struct git_transaction git_transaction;
+
+/** Annotated commits, the input to merge and rebase. */
+typedef struct git_annotated_commit git_annotated_commit;
+
+/** Merge result */
+typedef struct git_merge_result git_merge_result;
+
+/** Representation of a status collection */
+typedef struct git_status_list git_status_list;
+
+/** Representation of a rebase */
+typedef struct git_rebase git_rebase;
+
+/** Basic type of any Git reference. */
+typedef enum {
+ GIT_REF_INVALID = 0, /**< Invalid reference */
+ GIT_REF_OID = 1, /**< A reference which points at an object id */
+ GIT_REF_SYMBOLIC = 2, /**< A reference which points at another reference */
+ GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC,
+} git_ref_t;
+
+/** Basic type of any Git branch. */
+typedef enum {
+ GIT_BRANCH_LOCAL = 1,
+ GIT_BRANCH_REMOTE = 2,
+ GIT_BRANCH_ALL = GIT_BRANCH_LOCAL|GIT_BRANCH_REMOTE,
+} git_branch_t;
+
+/** Valid modes for index and tree entries. */
+typedef enum {
+ GIT_FILEMODE_UNREADABLE = 0000000,
+ GIT_FILEMODE_TREE = 0040000,
+ GIT_FILEMODE_BLOB = 0100644,
+ GIT_FILEMODE_BLOB_EXECUTABLE = 0100755,
+ GIT_FILEMODE_LINK = 0120000,
+ GIT_FILEMODE_COMMIT = 0160000,
+} git_filemode_t;
+
+/*
+ * A refspec specifies the mapping between remote and local reference
+ * names when fetch or pushing.
+ */
+typedef struct git_refspec git_refspec;
+
+/**
+ * Git's idea of a remote repository. A remote can be anonymous (in
+ * which case it does not have backing configuration entires).
+ */
+typedef struct git_remote git_remote;
+
+/**
+ * Interface which represents a transport to communicate with a
+ * remote.
+ */
+typedef struct git_transport git_transport;
+
+/**
+ * Preparation for a push operation. Can be used to configure what to
+ * push and the level of parallelism of the packfile builder.
+ */
+typedef struct git_push git_push;
+
+/* documentation in the definition */
+typedef struct git_remote_head git_remote_head;
+typedef struct git_remote_callbacks git_remote_callbacks;
+
+/**
+ * This is passed as the first argument to the callback to allow the
+ * user to see the progress.
+ *
+ * - total_objects: number of objects in the packfile being downloaded
+ * - indexed_objects: received objects that have been hashed
+ * - received_objects: objects which have been downloaded
+ * - local_objects: locally-available objects that have been injected
+ * in order to fix a thin pack.
+ * - received-bytes: size of the packfile received up to now
+ */
+typedef struct git_transfer_progress {
+ unsigned int total_objects;
+ unsigned int indexed_objects;
+ unsigned int received_objects;
+ unsigned int local_objects;
+ unsigned int total_deltas;
+ unsigned int indexed_deltas;
+ size_t received_bytes;
+} git_transfer_progress;
+
+/**
+ * Type for progress callbacks during indexing. Return a value less than zero
+ * to cancel the transfer.
+ *
+ * @param stats Structure containing information about the state of the transfer
+ * @param payload Payload provided by caller
+ */
+typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void *payload);
+
+/**
+ * Type for messages delivered by the transport. Return a negative value
+ * to cancel the network operation.
+ *
+ * @param str The message from the transport
+ * @param len The length of the message
+ * @param payload Payload provided by the caller
+ */
+typedef int (*git_transport_message_cb)(const char *str, int len, void *payload);
+
+
+/**
+ * Type of host certificate structure that is passed to the check callback
+ */
+typedef enum git_cert_t {
+ /**
+ * No information about the certificate is available. This may
+ * happen when using curl.
+ */
+ GIT_CERT_NONE,
+ /**
+ * The `data` argument to the callback will be a pointer to
+ * the DER-encoded data.
+ */
+ GIT_CERT_X509,
+ /**
+ * The `data` argument to the callback will be a pointer to a
+ * `git_cert_hostkey` structure.
+ */
+ GIT_CERT_HOSTKEY_LIBSSH2,
+ /**
+ * The `data` argument to the callback will be a pointer to a
+ * `git_strarray` with `name:content` strings containing
+ * information about the certificate. This is used when using
+ * curl.
+ */
+ GIT_CERT_STRARRAY,
+} git_cert_t;
+
+/**
+ * Parent type for `git_cert_hostkey` and `git_cert_x509`.
+ */
+typedef struct {
+ /**
+ * Type of certificate. A `GIT_CERT_` value.
+ */
+ git_cert_t cert_type;
+} git_cert;
+
+/**
+ * Callback for the user's custom certificate checks.
+ *
+ * @param cert The host certificate
+ * @param valid Whether the libgit2 checks (OpenSSL or WinHTTP) think
+ * this certificate is valid
+ * @param host Hostname of the host libgit2 connected to
+ * @param payload Payload provided by the caller
+ */
+typedef int (*git_transport_certificate_check_cb)(git_cert *cert, int valid, const char *host, void *payload);
+
+/**
+ * Opaque structure representing a submodule.
+ */
+typedef struct git_submodule git_submodule;
+
+/**
+ * Submodule update values
+ *
+ * These values represent settings for the `submodule.$name.update`
+ * configuration value which says how to handle `git submodule update` for
+ * this submodule. The value is usually set in the ".gitmodules" file and
+ * copied to ".git/config" when the submodule is initialized.
+ *
+ * You can override this setting on a per-submodule basis with
+ * `git_submodule_set_update()` and write the changed value to disk using
+ * `git_submodule_save()`. If you have overwritten the value, you can
+ * revert it by passing `GIT_SUBMODULE_UPDATE_RESET` to the set function.
+ *
+ * The values are:
+ *
+ * - GIT_SUBMODULE_UPDATE_CHECKOUT: the default; when a submodule is
+ * updated, checkout the new detached HEAD to the submodule directory.
+ * - GIT_SUBMODULE_UPDATE_REBASE: update by rebasing the current checked
+ * out branch onto the commit from the superproject.
+ * - GIT_SUBMODULE_UPDATE_MERGE: update by merging the commit in the
+ * superproject into the current checkout out branch of the submodule.
+ * - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when
+ * the commit in the superproject is updated.
+ * - GIT_SUBMODULE_UPDATE_DEFAULT: not used except as static initializer
+ * when we don't want any particular update rule to be specified.
+ */
+typedef enum {
+ GIT_SUBMODULE_UPDATE_CHECKOUT = 1,
+ GIT_SUBMODULE_UPDATE_REBASE = 2,
+ GIT_SUBMODULE_UPDATE_MERGE = 3,
+ GIT_SUBMODULE_UPDATE_NONE = 4,
+
+ GIT_SUBMODULE_UPDATE_DEFAULT = 0
+} git_submodule_update_t;
+
+/**
+ * Submodule ignore values
+ *
+ * These values represent settings for the `submodule.$name.ignore`
+ * configuration value which says how deeply to look at the working
+ * directory when getting submodule status.
+ *
+ * You can override this value in memory on a per-submodule basis with
+ * `git_submodule_set_ignore()` and can write the changed value to disk
+ * with `git_submodule_save()`. If you have overwritten the value, you
+ * can revert to the on disk value by using `GIT_SUBMODULE_IGNORE_RESET`.
+ *
+ * The values are:
+ *
+ * - GIT_SUBMODULE_IGNORE_UNSPECIFIED: use the submodule's configuration
+ * - GIT_SUBMODULE_IGNORE_NONE: don't ignore any change - i.e. even an
+ * untracked file, will mark the submodule as dirty. Ignored files are
+ * still ignored, of course.
+ * - GIT_SUBMODULE_IGNORE_UNTRACKED: ignore untracked files; only changes
+ * to tracked files, or the index or the HEAD commit will matter.
+ * - GIT_SUBMODULE_IGNORE_DIRTY: ignore changes in the working directory,
+ * only considering changes if the HEAD of submodule has moved from the
+ * value in the superproject.
+ * - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty
+ * - GIT_SUBMODULE_IGNORE_DEFAULT: not used except as static initializer
+ * when we don't want any particular ignore rule to be specified.
+ */
+typedef enum {
+ GIT_SUBMODULE_IGNORE_UNSPECIFIED = -1, /**< use the submodule's configuration */
+
+ GIT_SUBMODULE_IGNORE_NONE = 1, /**< any change or untracked == dirty */
+ GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /**< dirty if tracked files change */
+ GIT_SUBMODULE_IGNORE_DIRTY = 3, /**< only dirty if HEAD moved */
+ GIT_SUBMODULE_IGNORE_ALL = 4, /**< never dirty */
+} git_submodule_ignore_t;
+
+/**
+ * Options for submodule recurse.
+ *
+ * Represent the value of `submodule.$name.fetchRecurseSubmodules`
+ *
+ * * GIT_SUBMODULE_RECURSE_NO - do no recurse into submodules
+ * * GIT_SUBMODULE_RECURSE_YES - recurse into submodules
+ * * GIT_SUBMODULE_RECURSE_ONDEMAND - recurse into submodules only when
+ * commit not already in local clone
+ */
+typedef enum {
+ GIT_SUBMODULE_RECURSE_NO = 0,
+ GIT_SUBMODULE_RECURSE_YES = 1,
+ GIT_SUBMODULE_RECURSE_ONDEMAND = 2,
+} git_submodule_recurse_t;
+
+/** A type to write in a streaming fashion, for example, for filters. */
+typedef struct git_writestream git_writestream;
+
+struct git_writestream {
+ int (*write)(git_writestream *stream, const char *buffer, size_t len);
+ int (*close)(git_writestream *stream);
+ void (*free)(git_writestream *stream);
+};
+
+/** @} */
+GIT_END_DECL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_version_h__
+#define INCLUDE_git_version_h__
+
+#define LIBGIT2_VERSION "0.25.0"
+#define LIBGIT2_VER_MAJOR 0
+#define LIBGIT2_VER_MINOR 25
+#define LIBGIT2_VER_REVISION 0
+#define LIBGIT2_VER_PATCH 0
+
+#define LIBGIT2_SOVERSION 25
+
+#endif
--- /dev/null
+prefix=@PKGCONFIG_PREFIX@
+libdir=@PKGCONFIG_LIBDIR@
+includedir=@PKGCONFIG_INCLUDEDIR@
+
+Name: libgit2
+Description: The git library, take 2
+Version: @LIBGIT2_VERSION_STRING@
+
+Libs: -L"${libdir}" -lgit2
+Libs.private: @LIBGIT2_PC_LIBS@
+Requires.private: @LIBGIT2_PC_REQUIRES@
+
+Cflags: -I${includedir}
--- /dev/null
+{
+ ignore-zlib-errors-cond
+ Memcheck:Cond
+ obj:*libz.so*
+}
+
+{
+ ignore-giterr-set-leak
+ Memcheck:Leak
+ ...
+ fun:giterr_set
+}
+
+{
+ ignore-git-global-state-leak
+ Memcheck:Leak
+ ...
+ fun:git__global_state
+}
+
+{
+ ignore-openssl-ssl-leak
+ Memcheck:Leak
+ ...
+ obj:*libssl.so*
+ ...
+}
+
+{
+ ignore-openssl-crypto-leak
+ Memcheck:Leak
+ ...
+ obj:*libcrypto.so*
+ ...
+}
+
+{
+ ignore-openssl-crypto-cond
+ Memcheck:Cond
+ obj:*libcrypto.so*
+ ...
+}
+
+{
+ ignore-glibc-getaddrinfo-cache
+ Memcheck:Leak
+ ...
+ fun:__check_pf
+}
--- /dev/null
+#!/bin/sh
+set -e
+cd `dirname "$0"`/..
+if [ "$ARCH" = "i686" ]; then
+ f=i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z
+ if ! [ -e $f ]; then
+ curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/sjlj/$f
+ fi
+ 7z x $f > /dev/null
+ mv mingw32 /MinGW
+else
+ f=x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z
+ if ! [ -e $f ]; then
+ curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/$f
+ fi
+ 7z x $f > /dev/null
+ mv mingw64 /MinGW
+fi
+cd build
+cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON .. -G"$GENERATOR"
+cmake --build . --config RelWithDebInfo
--- /dev/null
+#!/bin/sh
+
+set -x
+
+if [ -n "$COVERITY" ];
+then
+ ./script/coverity.sh;
+ exit $?;
+fi
+
+if [ "$TRAVIS_OS_NAME" = "osx" ]; then
+ export PKG_CONFIG_PATH=$(ls -d /usr/local/Cellar/{curl,zlib}/*/lib/pkgconfig | paste -s -d':' -)
+fi
+
+# Should we ask Travis to cache this file?
+curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar || exit $?
+# Run this early so we know it's ready by the time we need it
+java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar &
+
+mkdir _build
+cd _build
+# shellcheck disable=SC2086
+cmake .. -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $?
+make -j2 install || exit $?
+
+# If this platform doesn't support test execution, bail out now
+if [ -n "$SKIP_TESTS" ];
+then
+ exit $?;
+fi
+
+# Create a test repo which we can use for the online::push tests
+mkdir "$HOME"/_temp
+git init --bare "$HOME"/_temp/test.git
+git daemon --listen=localhost --export-all --enable=receive-pack --base-path="$HOME"/_temp "$HOME"/_temp 2>/dev/null &
+export GITTEST_REMOTE_URL="git://localhost/test.git"
+
+# Run the test suite
+ctest -V -R libgit2_clar || exit $?
+
+# Now that we've tested the raw git protocol, let's set up ssh to we
+# can do the push tests over it
+
+killall git-daemon
+
+if [ "$TRAVIS_OS_NAME" = "osx" ]; then
+ echo 'PasswordAuthentication yes' | sudo tee -a /etc/sshd_config
+fi
+
+ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
+cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
+ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts
+
+# Get the fingerprint for localhost and remove the colons so we can parse it as
+# a hex number. The Mac version is newer so it has a different output format.
+if [ "$TRAVIS_OS_NAME" = "osx" ]; then
+ export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -E md5 -F localhost -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :)
+else
+ export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F localhost -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':')
+fi
+
+export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git"
+export GITTEST_REMOTE_USER=$USER
+export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa"
+export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub"
+export GITTEST_REMOTE_SSH_PASSPHRASE=""
+
+
+if [ -e ./libgit2_clar ]; then
+ ./libgit2_clar -sonline::push -sonline::clone::ssh_cert &&
+ ./libgit2_clar -sonline::clone::ssh_with_paths || exit $?
+ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
+ ./libgit2_clar -sonline::clone::cred_callback || exit $?
+ fi
+
+ # Use the proxy we started at the beginning
+ export GITTEST_REMOTE_PROXY_URL="http://foo:bar@localhost:8080/"
+ ./libgit2_clar -sonline::clone::proxy_credentials_in_url || exit $?
+ export GITTEST_REMOTE_PROXY_URL="http://localhost:8080/"
+ export GITTEST_REMOTE_PROXY_USER="foo"
+ export GITTEST_REMOTE_PROXY_PASS="bar"
+ ./libgit2_clar -sonline::clone::proxy_credentials_request || exit $?
+
+fi
+
+export GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent"
+export GITTEST_REMOTE_USER="libgit2test"
+ctest -V -R libgit2_clar-cred_callback
--- /dev/null
+#!/bin/bash
+set -e
+
+# Only run this on our branches
+echo "Branch: $TRAVIS_BRANCH | Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG"
+if [ "$TRAVIS_BRANCH" != "master" -o "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ];
+then
+ echo "Only analyzing the 'master' brach of the main repository."
+ exit 0
+fi
+
+# Environment check
+[ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1
+
+case $(uname -m) in
+ i?86) BITS=32 ;;
+ amd64|x86_64) BITS=64 ;;
+esac
+SCAN_TOOL=https://scan.coverity.com/download/cxx/linux${BITS}
+TOOL_BASE=$(pwd)/_coverity-scan
+
+# Install coverity tools
+if [ ! -d "$TOOL_BASE" ]; then
+ echo "Downloading coverity..."
+ mkdir -p "$TOOL_BASE"
+ pushd "$TOOL_BASE"
+ wget -O coverity_tool.tgz $SCAN_TOOL \
+ --post-data "project=libgit2&token=$COVERITY_TOKEN"
+ tar xzf coverity_tool.tgz
+ popd
+ TOOL_DIR=$(find "$TOOL_BASE" -type d -name 'cov-analysis*')
+ ln -s "$TOOL_DIR" "$TOOL_BASE"/cov-analysis
+fi
+
+cp script/user_nodefs.h "$TOOL_BASE"/cov-analysis/config/user_nodefs.h
+
+COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build"
+
+# Configure and build
+rm -rf _build
+mkdir _build
+cd _build
+cmake .. -DTHREADSAFE=ON
+COVERITY_UNSUPPORTED=1 \
+ $COV_BUILD --dir cov-int \
+ cmake --build .
+
+# Upload results
+tar czf libgit2.tgz cov-int
+SHA=$(git rev-parse --short HEAD)
+
+HTML="$(curl \
+ --silent \
+ --write-out "\n%{http_code}" \
+ --form token="$COVERITY_TOKEN" \
+ --form email=bs@github.com \
+ --form file=@libgit2.tgz \
+ --form version="$SHA" \
+ --form description="Travis build" \
+ https://scan.coverity.com/builds?project=libgit2)"
+# Body is everything up to the last line
+BODY="$(echo "$HTML" | head -n-1)"
+# Status code is the last line
+STATUS_CODE="$(echo "$HTML" | tail -n1)"
+
+echo "${BODY}"
+
+if [ "${STATUS_CODE}" != "201" ]; then
+ echo "Received error code ${STATUS_CODE} from Coverity"
+ exit 1
+fi
--- /dev/null
+#!/bin/sh
+
+set -x
+
+brew update
+brew install homebrew/dupes/zlib
+brew install curl
+brew install libssh2
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+void *realloc(void *ptr, size_t size);
+void *memmove(void *dest, const void *src, size_t n);
+size_t strlen(const char *s);
+
+typedef struct va_list_str *va_list;
+
+typedef struct git_vector {
+ void **contents;
+ size_t length;
+} git_vector;
+
+typedef struct git_buf {
+ char *ptr;
+ size_t asize, size;
+} git_buf;
+
+int git_vector_insert(git_vector *v, void *element)
+{
+ if (!v)
+ __coverity_panic__();
+
+ v->contents = realloc(v->contents, ++v->length);
+ if (!v->contents)
+ __coverity_panic__();
+ v->contents[v->length] = element;
+
+ return 0;
+}
+
+int git_buf_len(const struct git_buf *buf)
+{
+ return strlen(buf->ptr);
+}
+
+int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
+{
+ char ch, *s;
+ size_t len;
+
+ __coverity_string_null_sink__(format);
+ __coverity_string_size_sink__(format);
+
+ ch = *format;
+ ch = *(char *)ap;
+
+ buf->ptr = __coverity_alloc__(len);
+ __coverity_writeall__(buf->ptr);
+ buf->size = len;
+
+ return 0;
+}
+
+int git_buf_put(git_buf *buf, const char *data, size_t len)
+{
+ buf->ptr = __coverity_alloc__(buf->size + len + 1);
+ memmove(buf->ptr + buf->size, data, len);
+ buf->size += len;
+ buf->ptr[buf->size + len] = 0;
+ return 0;
+}
+
+int git_buf_set(git_buf *buf, const void *data, size_t len)
+{
+ buf->ptr = __coverity_alloc__(len + 1);
+ memmove(buf->ptr, data, len);
+ buf->size = len + 1;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#nodef GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { __coverity_panic__(); }
+#nodef GITERR_CHECK_ALLOC_BUF(buf) if (buf == NULL || git_buf_oom(buf)) { __coverity_panic__(); }
+
+#nodef GITERR_CHECK_ALLOC_ADD(out, one, two) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { __coverity_panic__(); }
+
+#nodef GITERR_CHECK_ALLOC_ADD3(out, one, two, three) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { __coverity_panic__(); }
+
+#nodef GITERR_CHECK_ALLOC_ADD4(out, one, two, three, four) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { __coverity_panic__(); }
+
+#nodef GITERR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { __coverity_panic__(); }
+
+#nodef GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) { __coverity_panic__(); }
+
+#nodef LOOKS_LIKE_DRIVE_PREFIX(S) (strlen(S) >= 2 && git__isalpha((S)[0]) && (S)[1] == ':')
+
+#nodef git_vector_foreach(v, iter, elem) \
+ for ((iter) = 0; (v)->contents != NULL && (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
+
+#nodef git_vector_rforeach(v, iter, elem) \
+ for ((iter) = (v)->length - 1; (v)->contents != NULL && (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- )
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "annotated_commit.h"
+#include "refs.h"
+#include "cache.h"
+
+#include "git2/commit.h"
+#include "git2/refs.h"
+#include "git2/repository.h"
+#include "git2/annotated_commit.h"
+#include "git2/revparse.h"
+#include "git2/tree.h"
+#include "git2/index.h"
+
+static int annotated_commit_init(
+ git_annotated_commit **out,
+ git_commit *commit,
+ const char *description)
+{
+ git_annotated_commit *annotated_commit;
+ int error = 0;
+
+ assert(out && commit);
+
+ *out = NULL;
+
+ annotated_commit = git__calloc(1, sizeof(git_annotated_commit));
+ GITERR_CHECK_ALLOC(annotated_commit);
+
+ annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL;
+
+ if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0)
+ goto done;
+
+ git_oid_fmt(annotated_commit->id_str, git_commit_id(commit));
+ annotated_commit->id_str[GIT_OID_HEXSZ] = '\0';
+
+ if (!description)
+ description = annotated_commit->id_str;
+
+ annotated_commit->description = git__strdup(description);
+ GITERR_CHECK_ALLOC(annotated_commit->description);
+
+done:
+ if (!error)
+ *out = annotated_commit;
+
+ return error;
+}
+
+static int annotated_commit_init_from_id(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_oid *id,
+ const char *description)
+{
+ git_commit *commit = NULL;
+ int error = 0;
+
+ assert(out && repo && id);
+
+ *out = NULL;
+
+ if ((error = git_commit_lookup(&commit, repo, id)) < 0)
+ goto done;
+
+ error = annotated_commit_init(out, commit, description);
+
+done:
+ git_commit_free(commit);
+ return error;
+}
+
+int git_annotated_commit_lookup(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_oid *id)
+{
+ return annotated_commit_init_from_id(out, repo, id, NULL);
+}
+
+int git_annotated_commit_from_commit(
+ git_annotated_commit **out,
+ git_commit *commit)
+{
+ return annotated_commit_init(out, commit, NULL);
+}
+
+int git_annotated_commit_from_revspec(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const char *revspec)
+{
+ git_object *obj, *commit;
+ int error;
+
+ assert(out && repo && revspec);
+
+ if ((error = git_revparse_single(&obj, repo, revspec)) < 0)
+ return error;
+
+ if ((error = git_object_peel(&commit, obj, GIT_OBJ_COMMIT))) {
+ git_object_free(obj);
+ return error;
+ }
+
+ error = annotated_commit_init(out, (git_commit *)commit, revspec);
+
+ git_object_free(obj);
+ git_object_free(commit);
+
+ return error;
+}
+
+int git_annotated_commit_from_ref(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_reference *ref)
+{
+ git_reference *resolved;
+ int error = 0;
+
+ assert(out && repo && ref);
+
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return error;
+
+ error = annotated_commit_init_from_id(out,
+ repo,
+ git_reference_target(resolved),
+ git_reference_name(ref));
+
+ if (!error) {
+ (*out)->ref_name = git__strdup(git_reference_name(ref));
+ GITERR_CHECK_ALLOC((*out)->ref_name);
+ }
+
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_annotated_commit_from_head(
+ git_annotated_commit **out,
+ git_repository *repo)
+{
+ git_reference *head;
+ int error;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
+ return -1;
+
+ error = git_annotated_commit_from_ref(out, repo, head);
+
+ git_reference_free(head);
+ return error;
+}
+
+int git_annotated_commit_from_fetchhead(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_url,
+ const git_oid *id)
+{
+ assert(repo && id && branch_name && remote_url);
+
+ if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0)
+ return -1;
+
+ (*out)->ref_name = git__strdup(branch_name);
+ GITERR_CHECK_ALLOC((*out)->ref_name);
+
+ (*out)->remote_url = git__strdup(remote_url);
+ GITERR_CHECK_ALLOC((*out)->remote_url);
+
+ return 0;
+}
+
+
+const git_oid *git_annotated_commit_id(
+ const git_annotated_commit *annotated_commit)
+{
+ assert(annotated_commit);
+ return git_commit_id(annotated_commit->commit);
+}
+
+void git_annotated_commit_free(git_annotated_commit *annotated_commit)
+{
+ if (annotated_commit == NULL)
+ return;
+
+ switch (annotated_commit->type) {
+ case GIT_ANNOTATED_COMMIT_REAL:
+ git_commit_free(annotated_commit->commit);
+ git_tree_free(annotated_commit->tree);
+ git__free((char *)annotated_commit->description);
+ git__free((char *)annotated_commit->ref_name);
+ git__free((char *)annotated_commit->remote_url);
+ break;
+ case GIT_ANNOTATED_COMMIT_VIRTUAL:
+ git_index_free(annotated_commit->index);
+ git_array_clear(annotated_commit->parents);
+ break;
+ default:
+ abort();
+ }
+
+ git__free(annotated_commit);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_annotated_commit_h__
+#define INCLUDE_annotated_commit_h__
+
+#include "oidarray.h"
+
+#include "git2/oid.h"
+
+typedef enum {
+ GIT_ANNOTATED_COMMIT_REAL = 1,
+ GIT_ANNOTATED_COMMIT_VIRTUAL = 2,
+} git_annotated_commit_t;
+
+/**
+ * Internal structure for merge inputs. An annotated commit is generally
+ * "real" and backed by an actual commit in the repository, but merge will
+ * internally create "virtual" commits that are in-memory intermediate
+ * commits backed by an index.
+ */
+struct git_annotated_commit {
+ git_annotated_commit_t type;
+
+ /* real commit */
+ git_commit *commit;
+ git_tree *tree;
+
+ /* virtual commit structure */
+ git_index *index;
+ git_array_oid_t parents;
+
+ /* how this commit was looked up */
+ const char *description;
+
+ const char *ref_name;
+ const char *remote_url;
+
+ char id_str[GIT_OID_HEXSZ+1];
+};
+
+extern int git_annotated_commit_from_head(git_annotated_commit **out,
+ git_repository *repo);
+extern int git_annotated_commit_from_commit(git_annotated_commit **out,
+ git_commit *commit);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "git2/patch.h"
+#include "git2/filter.h"
+#include "array.h"
+#include "patch.h"
+#include "fileops.h"
+#include "apply.h"
+#include "delta.h"
+#include "zstream.h"
+
+#define apply_err(...) \
+ ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
+
+typedef struct {
+ /* The lines that we allocate ourself are allocated out of the pool.
+ * (Lines may have been allocated out of the diff.)
+ */
+ git_pool pool;
+ git_vector lines;
+} patch_image;
+
+static void patch_line_init(
+ git_diff_line *out,
+ const char *in,
+ size_t in_len,
+ size_t in_offset)
+{
+ out->content = in;
+ out->content_len = in_len;
+ out->content_offset = in_offset;
+}
+
+#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT }
+
+static int patch_image_init_fromstr(
+ patch_image *out, const char *in, size_t in_len)
+{
+ git_diff_line *line;
+ const char *start, *end;
+
+ memset(out, 0x0, sizeof(patch_image));
+
+ git_pool_init(&out->pool, sizeof(git_diff_line));
+
+ for (start = in; start < in + in_len; start = end) {
+ end = memchr(start, '\n', in_len);
+
+ if (end == NULL)
+ end = in + in_len;
+
+ else if (end < in + in_len)
+ end++;
+
+ line = git_pool_mallocz(&out->pool, 1);
+ GITERR_CHECK_ALLOC(line);
+
+ if (git_vector_insert(&out->lines, line) < 0)
+ return -1;
+
+ patch_line_init(line, start, (end - start), (start - in));
+ }
+
+ return 0;
+}
+
+static void patch_image_free(patch_image *image)
+{
+ if (image == NULL)
+ return;
+
+ git_pool_clear(&image->pool);
+ git_vector_free(&image->lines);
+}
+
+static bool match_hunk(
+ patch_image *image,
+ patch_image *preimage,
+ size_t linenum)
+{
+ bool match = 0;
+ size_t i;
+
+ /* Ensure this hunk is within the image boundaries. */
+ if (git_vector_length(&preimage->lines) + linenum >
+ git_vector_length(&image->lines))
+ return 0;
+
+ match = 1;
+
+ /* Check exact match. */
+ for (i = 0; i < git_vector_length(&preimage->lines); i++) {
+ git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
+ git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
+
+ if (preimage_line->content_len != image_line->content_len ||
+ memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
+ match = 0;
+ break;
+ }
+ }
+
+ return match;
+}
+
+static bool find_hunk_linenum(
+ size_t *out,
+ patch_image *image,
+ patch_image *preimage,
+ size_t linenum)
+{
+ size_t max = git_vector_length(&image->lines);
+ bool match;
+
+ if (linenum > max)
+ linenum = max;
+
+ match = match_hunk(image, preimage, linenum);
+
+ *out = linenum;
+ return match;
+}
+
+static int update_hunk(
+ patch_image *image,
+ unsigned int linenum,
+ patch_image *preimage,
+ patch_image *postimage)
+{
+ size_t postlen = git_vector_length(&postimage->lines);
+ size_t prelen = git_vector_length(&preimage->lines);
+ size_t i;
+ int error = 0;
+
+ if (postlen > prelen)
+ error = git_vector_insert_null(
+ &image->lines, linenum, (postlen - prelen));
+ else if (prelen > postlen)
+ error = git_vector_remove_range(
+ &image->lines, linenum, (prelen - postlen));
+
+ if (error) {
+ giterr_set_oom();
+ return -1;
+ }
+
+ for (i = 0; i < git_vector_length(&postimage->lines); i++) {
+ image->lines.contents[linenum + i] =
+ git_vector_get(&postimage->lines, i);
+ }
+
+ return 0;
+}
+
+static int apply_hunk(
+ patch_image *image,
+ git_patch *patch,
+ git_patch_hunk *hunk)
+{
+ patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT;
+ size_t line_num, i;
+ int error = 0;
+
+ for (i = 0; i < hunk->line_count; i++) {
+ size_t linenum = hunk->line_start + i;
+ git_diff_line *line = git_array_get(patch->lines, linenum);
+
+ if (!line) {
+ error = apply_err("Preimage does not contain line %"PRIuZ, linenum);
+ goto done;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+ line->origin == GIT_DIFF_LINE_DELETION) {
+ if ((error = git_vector_insert(&preimage.lines, line)) < 0)
+ goto done;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+ line->origin == GIT_DIFF_LINE_ADDITION) {
+ if ((error = git_vector_insert(&postimage.lines, line)) < 0)
+ goto done;
+ }
+ }
+
+ line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0;
+
+ if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
+ error = apply_err("Hunk at line %d did not apply",
+ hunk->hunk.new_start);
+ goto done;
+ }
+
+ error = update_hunk(image, line_num, &preimage, &postimage);
+
+done:
+ patch_image_free(&preimage);
+ patch_image_free(&postimage);
+
+ return error;
+}
+
+static int apply_hunks(
+ git_buf *out,
+ const char *source,
+ size_t source_len,
+ git_patch *patch)
+{
+ git_patch_hunk *hunk;
+ git_diff_line *line;
+ patch_image image;
+ size_t i;
+ int error = 0;
+
+ if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
+ goto done;
+
+ git_array_foreach(patch->hunks, i, hunk) {
+ if ((error = apply_hunk(&image, patch, hunk)) < 0)
+ goto done;
+ }
+
+ git_vector_foreach(&image.lines, i, line)
+ git_buf_put(out, line->content, line->content_len);
+
+done:
+ patch_image_free(&image);
+
+ return error;
+}
+
+static int apply_binary_delta(
+ git_buf *out,
+ const char *source,
+ size_t source_len,
+ git_diff_binary_file *binary_file)
+{
+ git_buf inflated = GIT_BUF_INIT;
+ int error = 0;
+
+ /* no diff means identical contents */
+ if (binary_file->datalen == 0)
+ return git_buf_put(out, source, source_len);
+
+ error = git_zstream_inflatebuf(&inflated,
+ binary_file->data, binary_file->datalen);
+
+ if (!error && inflated.size != binary_file->inflatedlen) {
+ error = apply_err("inflated delta does not match expected length");
+ git_buf_free(out);
+ }
+
+ if (error < 0)
+ goto done;
+
+ if (binary_file->type == GIT_DIFF_BINARY_DELTA) {
+ void *data;
+ size_t data_len;
+
+ error = git_delta_apply(&data, &data_len, (void *)source, source_len,
+ (void *)inflated.ptr, inflated.size);
+
+ out->ptr = data;
+ out->size = data_len;
+ out->asize = data_len;
+ }
+ else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) {
+ git_buf_swap(out, &inflated);
+ }
+ else {
+ error = apply_err("unknown binary delta type");
+ goto done;
+ }
+
+done:
+ git_buf_free(&inflated);
+ return error;
+}
+
+static int apply_binary(
+ git_buf *out,
+ const char *source,
+ size_t source_len,
+ git_patch *patch)
+{
+ git_buf reverse = GIT_BUF_INIT;
+ int error = 0;
+
+ if (!patch->binary.contains_data) {
+ error = apply_err("patch does not contain binary data");
+ goto done;
+ }
+
+ if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen)
+ goto done;
+
+ /* first, apply the new_file delta to the given source */
+ if ((error = apply_binary_delta(out, source, source_len,
+ &patch->binary.new_file)) < 0)
+ goto done;
+
+ /* second, apply the old_file delta to sanity check the result */
+ if ((error = apply_binary_delta(&reverse, out->ptr, out->size,
+ &patch->binary.old_file)) < 0)
+ goto done;
+
+ if (source_len != reverse.size ||
+ memcmp(source, reverse.ptr, source_len) != 0) {
+ error = apply_err("binary patch did not apply cleanly");
+ goto done;
+ }
+
+done:
+ if (error < 0)
+ git_buf_free(out);
+
+ git_buf_free(&reverse);
+ return error;
+}
+
+int git_apply__patch(
+ git_buf *contents_out,
+ char **filename_out,
+ unsigned int *mode_out,
+ const char *source,
+ size_t source_len,
+ git_patch *patch)
+{
+ char *filename = NULL;
+ unsigned int mode = 0;
+ int error = 0;
+
+ assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
+
+ *filename_out = NULL;
+ *mode_out = 0;
+
+ if (patch->delta->status != GIT_DELTA_DELETED) {
+ const git_diff_file *newfile = &patch->delta->new_file;
+
+ filename = git__strdup(newfile->path);
+ mode = newfile->mode ?
+ newfile->mode : GIT_FILEMODE_BLOB;
+ }
+
+ if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
+ error = apply_binary(contents_out, source, source_len, patch);
+ else if (patch->hunks.size)
+ error = apply_hunks(contents_out, source, source_len, patch);
+ else
+ error = git_buf_put(contents_out, source, source_len);
+
+ if (error)
+ goto done;
+
+ if (patch->delta->status == GIT_DELTA_DELETED &&
+ git_buf_len(contents_out) > 0) {
+ error = apply_err("removal patch leaves file contents");
+ goto done;
+ }
+
+ *filename_out = filename;
+ *mode_out = mode;
+
+done:
+ if (error < 0)
+ git__free(filename);
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_apply_h__
+#define INCLUDE_apply_h__
+
+#include "git2/patch.h"
+#include "buffer.h"
+
+extern int git_apply__patch(
+ git_buf *out,
+ char **filename,
+ unsigned int *mode,
+ const char *source,
+ size_t source_len,
+ git_patch *patch);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_array_h__
+#define INCLUDE_array_h__
+
+#include "common.h"
+
+/*
+ * Use this to declare a typesafe resizable array of items, a la:
+ *
+ * git_array_t(int) my_ints = GIT_ARRAY_INIT;
+ * ...
+ * int *i = git_array_alloc(my_ints);
+ * GITERR_CHECK_ALLOC(i);
+ * ...
+ * git_array_clear(my_ints);
+ *
+ * You may also want to do things like:
+ *
+ * typedef git_array_t(my_struct) my_struct_array_t;
+ */
+#define git_array_t(type) struct { type *ptr; size_t size, asize; }
+
+#define GIT_ARRAY_INIT { NULL, 0, 0 }
+
+#define git_array_init(a) \
+ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
+
+#define git_array_init_to_size(a, desired) \
+ do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
+
+#define git_array_clear(a) \
+ do { git__free((a).ptr); git_array_init(a); } while (0)
+
+#define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr)
+
+
+typedef git_array_t(char) git_array_generic_t;
+
+/* use a generic array for growth so this can return the new item */
+GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
+{
+ volatile git_array_generic_t *a = _a;
+ size_t new_size;
+ char *new_array;
+
+ if (a->size < 8) {
+ new_size = 8;
+ } else {
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3))
+ goto on_oom;
+ new_size /= 2;
+ }
+
+ if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL)
+ goto on_oom;
+
+ a->ptr = new_array; a->asize = new_size; a->size++;
+ return a->ptr + (a->size - 1) * item_size;
+
+on_oom:
+ git_array_clear(*a);
+ return NULL;
+}
+
+#define git_array_alloc(a) \
+ (((a).size >= (a).asize) ? \
+ git_array_grow(&(a), sizeof(*(a).ptr)) : \
+ ((a).ptr ? &(a).ptr[(a).size++] : NULL))
+
+#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
+
+#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL)
+
+#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
+
+#define git_array_size(a) (a).size
+
+#define git_array_valid_index(a, i) ((i) < (a).size)
+
+#define git_array_foreach(a, i, element) \
+ for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
+
+GIT_INLINE(int) git_array__search(
+ size_t *out,
+ void *array_ptr,
+ size_t item_size,
+ size_t array_len,
+ int (*compare)(const void *, const void *),
+ const void *key)
+{
+ size_t lim;
+ unsigned char *part, *array = array_ptr, *base = array_ptr;
+ int cmp = -1;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1) * item_size;
+ cmp = (*compare)(key, part);
+
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1 * item_size;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (out)
+ *out = (base - array) / item_size;
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+#define git_array_search(out, a, cmp, key) \
+ git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \
+ (cmp), (key))
+
+#endif
--- /dev/null
+#include "common.h"
+#include "repository.h"
+#include "sysdir.h"
+#include "config.h"
+#include "attr_file.h"
+#include "ignore.h"
+#include "git2/oid.h"
+#include <ctype.h>
+
+GIT__USE_STRMAP
+
+const char *git_attr__true = "[internal]__TRUE__";
+const char *git_attr__false = "[internal]__FALSE__";
+const char *git_attr__unset = "[internal]__UNSET__";
+
+git_attr_t git_attr_value(const char *attr)
+{
+ if (attr == NULL || attr == git_attr__unset)
+ return GIT_ATTR_UNSPECIFIED_T;
+
+ if (attr == git_attr__true)
+ return GIT_ATTR_TRUE_T;
+
+ if (attr == git_attr__false)
+ return GIT_ATTR_FALSE_T;
+
+ return GIT_ATTR_VALUE_T;
+}
+
+static int collect_attr_files(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ uint32_t flags,
+ const char *path,
+ git_vector *files);
+
+static void release_attr_files(git_vector *files);
+
+int git_attr_get(
+ const char **value,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ const char *name)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j;
+ git_attr_file *file;
+ git_attr_name attr;
+ git_attr_rule *rule;
+
+ assert(value && repo && name);
+
+ *value = NULL;
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.name = name;
+ attr.name_hash = git_attr_file__name_hash(name);
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+ size_t pos;
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
+ *value = ((git_attr_assignment *)git_vector_get(
+ &rule->assigns, pos))->value;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+
+ return error;
+}
+
+
+typedef struct {
+ git_attr_name name;
+ git_attr_assignment *found;
+} attr_get_many_info;
+
+int git_attr_get_many_with_session(
+ const char **values,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ uint32_t flags,
+ const char *pathname,
+ size_t num_attr,
+ const char **names)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ attr_get_many_info *info = NULL;
+ size_t num_found = 0;
+
+ if (!num_attr)
+ return 0;
+
+ assert(values && repo && names);
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
+ goto cleanup;
+
+ info = git__calloc(num_attr, sizeof(attr_get_many_info));
+ GITERR_CHECK_ALLOC(info);
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ for (k = 0; k < num_attr; k++) {
+ size_t pos;
+
+ if (info[k].found != NULL) /* already found assignment */
+ continue;
+
+ if (!info[k].name.name) {
+ info[k].name.name = names[k];
+ info[k].name.name_hash = git_attr_file__name_hash(names[k]);
+ }
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
+ info[k].found = (git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos);
+ values[k] = info[k].found->value;
+
+ if (++num_found == num_attr)
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+ for (k = 0; k < num_attr; k++) {
+ if (!info[k].found)
+ values[k] = NULL;
+ }
+
+cleanup:
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+ git__free(info);
+
+ return error;
+}
+
+int git_attr_get_many(
+ const char **values,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ size_t num_attr,
+ const char **names)
+{
+ return git_attr_get_many_with_session(
+ values, repo, NULL, flags, pathname, num_attr, names);
+}
+
+int git_attr_foreach(
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ int (*callback)(const char *name, const char *value, void *payload),
+ void *payload)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+ git_strmap *seen = NULL;
+
+ assert(repo && callback);
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
+ (error = git_strmap_alloc(&seen)) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ git_vector_foreach(&rule->assigns, k, assign) {
+ /* skip if higher priority assignment was already seen */
+ if (git_strmap_exists(seen, assign->name))
+ continue;
+
+ git_strmap_insert(seen, assign->name, assign, error);
+ if (error < 0)
+ goto cleanup;
+
+ error = callback(assign->name, assign->value, payload);
+ if (error) {
+ giterr_set_after_callback(error);
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+cleanup:
+ git_strmap_free(seen);
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+
+ return error;
+}
+
+static int preload_attr_file(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_source source,
+ const char *base,
+ const char *file)
+{
+ int error;
+ git_attr_file *preload = NULL;
+
+ if (!file)
+ return 0;
+ if (!(error = git_attr_cache__get(
+ &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
+ git_attr_file__free(preload);
+
+ return error;
+}
+
+static int system_attr_file(
+ git_buf *out,
+ git_attr_session *attr_session)
+{
+ int error;
+
+ if (!attr_session) {
+ error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
+
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+
+ return error;
+ }
+
+ if (!attr_session->init_sysdir) {
+ error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
+
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error)
+ return error;
+
+ attr_session->init_sysdir = 1;
+ }
+
+ if (attr_session->sysdir.size == 0)
+ return GIT_ENOTFOUND;
+
+ /* We can safely provide a git_buf with no allocation (asize == 0) to
+ * a consumer. This allows them to treat this as a regular `git_buf`,
+ * but their call to `git_buf_free` will not attempt to free it.
+ */
+ git_buf_attach_notowned(
+ out, attr_session->sysdir.ptr, attr_session->sysdir.size);
+ return 0;
+}
+
+static int attr_setup(git_repository *repo, git_attr_session *attr_session)
+{
+ int error = 0;
+ const char *workdir = git_repository_workdir(repo);
+ git_index *idx = NULL;
+ git_buf sys = GIT_BUF_INIT;
+
+ if (attr_session && attr_session->init_setup)
+ return 0;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ /* preload attribute files that could contain macros so the
+ * definitions will be available for later file parsing
+ */
+
+ error = system_attr_file(&sys, attr_session);
+
+ if (error == 0)
+ error = preload_attr_file(
+ repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ git_buf_free(&sys);
+
+ if ((error = preload_attr_file(
+ repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
+ NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
+ return error;
+
+ if ((error = preload_attr_file(
+ repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
+ git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
+ return error;
+
+ if (workdir != NULL &&
+ (error = preload_attr_file(
+ repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
+ return error;
+
+ if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+ (error = preload_attr_file(
+ repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
+ return error;
+
+ if (attr_session)
+ attr_session->init_setup = 1;
+
+ return error;
+}
+
+int git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values)
+{
+ int error;
+ git_attr_rule *macro = NULL;
+ git_pool *pool;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ macro = git__calloc(1, sizeof(git_attr_rule));
+ GITERR_CHECK_ALLOC(macro);
+
+ pool = &git_repository_attr_cache(repo)->pool;
+
+ macro->match.pattern = git_pool_strdup(pool, name);
+ GITERR_CHECK_ALLOC(macro->match.pattern);
+
+ macro->match.length = strlen(macro->match.pattern);
+ macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
+
+ error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values);
+
+ if (!error)
+ error = git_attr_cache__insert_macro(repo, macro);
+
+ if (error < 0)
+ git_attr_rule__free(macro);
+
+ return error;
+}
+
+typedef struct {
+ git_repository *repo;
+ git_attr_session *attr_session;
+ uint32_t flags;
+ const char *workdir;
+ git_index *index;
+ git_vector *files;
+} attr_walk_up_info;
+
+static int attr_decide_sources(
+ uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
+{
+ int count = 0;
+
+ switch (flags & 0x03) {
+ case GIT_ATTR_CHECK_FILE_THEN_INDEX:
+ if (has_wd)
+ srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ break;
+ case GIT_ATTR_CHECK_INDEX_THEN_FILE:
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ if (has_wd)
+ srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
+ break;
+ case GIT_ATTR_CHECK_INDEX_ONLY:
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ break;
+ }
+
+ return count;
+}
+
+static int push_attr_file(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_vector *list,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_attr_file *file = NULL;
+
+ error = git_attr_cache__get(&file, repo, attr_session,
+ source, base, filename, git_attr_file__parse_buffer);
+
+ if (error < 0)
+ return error;
+
+ if (file != NULL) {
+ if ((error = git_vector_insert(list, file)) < 0)
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+static int push_one_attr(void *ref, const char *path)
+{
+ int error = 0, n_src, i;
+ attr_walk_up_info *info = (attr_walk_up_info *)ref;
+ git_attr_file_source src[2];
+
+ n_src = attr_decide_sources(
+ info->flags, info->workdir != NULL, info->index != NULL, src);
+
+ for (i = 0; !error && i < n_src; ++i)
+ error = push_attr_file(info->repo, info->attr_session,
+ info->files, src[i], path, GIT_ATTR_FILE);
+
+ return error;
+}
+
+static void release_attr_files(git_vector *files)
+{
+ size_t i;
+ git_attr_file *file;
+
+ git_vector_foreach(files, i, file) {
+ git_attr_file__free(file);
+ files->contents[i] = NULL;
+ }
+ git_vector_free(files);
+}
+
+static int collect_attr_files(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ uint32_t flags,
+ const char *path,
+ git_vector *files)
+{
+ int error = 0;
+ git_buf dir = GIT_BUF_INIT;
+ const char *workdir = git_repository_workdir(repo);
+ attr_walk_up_info info = { NULL };
+
+ if ((error = attr_setup(repo, attr_session)) < 0)
+ return error;
+
+ /* Resolve path in a non-bare repo */
+ if (workdir != NULL)
+ error = git_path_find_dir(&dir, path, workdir);
+ else
+ error = git_path_dirname_r(&dir, path);
+ if (error < 0)
+ goto cleanup;
+
+ /* in precendence order highest to lowest:
+ * - $GIT_DIR/info/attributes
+ * - path components with .gitattributes
+ * - config core.attributesfile
+ * - $GIT_PREFIX/etc/gitattributes
+ */
+
+ error = push_attr_file(
+ repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
+ git_repository_path(repo), GIT_ATTR_FILE_INREPO);
+ if (error < 0)
+ goto cleanup;
+
+ info.repo = repo;
+ info.attr_session = attr_session;
+ info.flags = flags;
+ info.workdir = workdir;
+ if (git_repository_index__weakptr(&info.index, repo) < 0)
+ giterr_clear(); /* no error even if there is no index */
+ info.files = files;
+
+ if (!strcmp(dir.ptr, "."))
+ error = push_one_attr(&info, "");
+ else
+ error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
+
+ if (error < 0)
+ goto cleanup;
+
+ if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
+ error = push_attr_file(
+ repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
+ NULL, git_repository_attr_cache(repo)->cfg_attr_file);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
+ error = system_attr_file(&dir, attr_session);
+
+ if (!error)
+ error = push_attr_file(
+ repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
+ NULL, dir.ptr);
+ else if (error == GIT_ENOTFOUND)
+ error = 0;
+ }
+
+ cleanup:
+ if (error < 0)
+ release_attr_files(files);
+ git_buf_free(&dir);
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attr_h__
+#define INCLUDE_attr_h__
+
+#include "attr_file.h"
+#include "attrcache.h"
+
+#endif
--- /dev/null
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include "attr_file.h"
+#include "attrcache.h"
+#include "git2/blob.h"
+#include "git2/tree.h"
+#include "index.h"
+#include <ctype.h>
+
+static void attr_file_free(git_attr_file *file)
+{
+ bool unlock = !git_mutex_lock(&file->lock);
+ git_attr_file__clear_rules(file, false);
+ git_pool_clear(&file->pool);
+ if (unlock)
+ git_mutex_unlock(&file->lock);
+ git_mutex_free(&file->lock);
+
+ git__memzero(file, sizeof(*file));
+ git__free(file);
+}
+
+int git_attr_file__new(
+ git_attr_file **out,
+ git_attr_file_entry *entry,
+ git_attr_file_source source)
+{
+ git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
+ GITERR_CHECK_ALLOC(attrs);
+
+ if (git_mutex_init(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to initialize lock");
+ git__free(attrs);
+ return -1;
+ }
+
+ git_pool_init(&attrs->pool, 1);
+ GIT_REFCOUNT_INC(attrs);
+ attrs->entry = entry;
+ attrs->source = source;
+ *out = attrs;
+ return 0;
+}
+
+int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
+{
+ unsigned int i;
+ git_attr_rule *rule;
+
+ if (need_lock && git_mutex_lock(&file->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock attribute file");
+ return -1;
+ }
+
+ git_vector_foreach(&file->rules, i, rule)
+ git_attr_rule__free(rule);
+ git_vector_free(&file->rules);
+
+ if (need_lock)
+ git_mutex_unlock(&file->lock);
+
+ return 0;
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+ if (!file)
+ return;
+ GIT_REFCOUNT_DEC(file, attr_file_free);
+}
+
+static int attr_file_oid_from_index(
+ git_oid *oid, git_repository *repo, const char *path)
+{
+ int error;
+ git_index *idx;
+ size_t pos;
+ const git_index_entry *entry;
+
+ if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+ (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
+ return error;
+
+ if (!(entry = git_index_get_byindex(idx, pos)))
+ return GIT_ENOTFOUND;
+
+ *oid = entry->id;
+ return 0;
+}
+
+int git_attr_file__load(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_entry *entry,
+ git_attr_file_source source,
+ git_attr_file_parser parser)
+{
+ int error = 0;
+ git_blob *blob = NULL;
+ git_buf content = GIT_BUF_INIT;
+ git_attr_file *file;
+ struct stat st;
+ bool nonexistent = false;
+
+ *out = NULL;
+
+ switch (source) {
+ case GIT_ATTR_FILE__IN_MEMORY:
+ /* in-memory attribute file doesn't need data */
+ break;
+ case GIT_ATTR_FILE__FROM_INDEX: {
+ git_oid id;
+
+ if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
+ (error = git_blob_lookup(&blob, repo, &id)) < 0)
+ return error;
+
+ /* Do not assume that data straight from the ODB is NULL-terminated;
+ * copy the contents of a file to a buffer to work on */
+ git_buf_put(&content, git_blob_rawcontent(blob), git_blob_rawsize(blob));
+ break;
+ }
+ case GIT_ATTR_FILE__FROM_FILE: {
+ int fd = -1;
+
+ /* For open or read errors, pretend that we got ENOTFOUND. */
+ /* TODO: issue warning when warning API is available */
+
+ if (p_stat(entry->fullpath, &st) < 0 ||
+ S_ISDIR(st.st_mode) ||
+ (fd = git_futils_open_ro(entry->fullpath)) < 0 ||
+ (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
+ nonexistent = true;
+
+ if (fd >= 0)
+ p_close(fd);
+
+ break;
+ }
+ default:
+ giterr_set(GITERR_INVALID, "Unknown file source %d", source);
+ return -1;
+ }
+
+ if ((error = git_attr_file__new(&file, entry, source)) < 0)
+ goto cleanup;
+
+ /* store the key of the attr_reader; don't bother with cache
+ * invalidation during the same attr reader session.
+ */
+ if (attr_session)
+ file->session_key = attr_session->key;
+
+ if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
+ git_attr_file__free(file);
+ goto cleanup;
+ }
+
+ /* write cache breakers */
+ if (nonexistent)
+ file->nonexistent = 1;
+ else if (source == GIT_ATTR_FILE__FROM_INDEX)
+ git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
+ else if (source == GIT_ATTR_FILE__FROM_FILE)
+ git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
+ /* else always cacheable */
+
+ *out = file;
+
+cleanup:
+ git_blob_free(blob);
+ git_buf_free(&content);
+
+ return error;
+}
+
+int git_attr_file__out_of_date(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file *file)
+{
+ if (!file)
+ return 1;
+
+ /* we are never out of date if we just created this data in the same
+ * attr_session; otherwise, nonexistent files must be invalidated
+ */
+ if (attr_session && attr_session->key == file->session_key)
+ return 0;
+ else if (file->nonexistent)
+ return 1;
+
+ switch (file->source) {
+ case GIT_ATTR_FILE__IN_MEMORY:
+ return 0;
+
+ case GIT_ATTR_FILE__FROM_FILE:
+ return git_futils_filestamp_check(
+ &file->cache_data.stamp, file->entry->fullpath);
+
+ case GIT_ATTR_FILE__FROM_INDEX: {
+ int error;
+ git_oid id;
+
+ if ((error = attr_file_oid_from_index(
+ &id, repo, file->entry->path)) < 0)
+ return error;
+
+ return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
+ }
+
+ default:
+ giterr_set(GITERR_INVALID, "Invalid file type %d", file->source);
+ return -1;
+ }
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
+static void git_attr_rule__clear(git_attr_rule *rule);
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern);
+
+int git_attr_file__parse_buffer(
+ git_repository *repo, git_attr_file *attrs, const char *data)
+{
+ int error = 0;
+ const char *scan = data, *context = NULL;
+ git_attr_rule *rule = NULL;
+
+ /* if subdir file path, convert context for file paths */
+ if (attrs->entry &&
+ git_path_root(attrs->entry->path) < 0 &&
+ !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
+ context = attrs->entry->path;
+
+ if (git_mutex_lock(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock attribute file");
+ return -1;
+ }
+
+ while (!error && *scan) {
+ /* allocate rule if needed */
+ if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
+ error = -1;
+ break;
+ }
+
+ rule->match.flags =
+ GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
+
+ /* parse the next "pattern attr attr attr" line */
+ if (!(error = git_attr_fnmatch__parse(
+ &rule->match, &attrs->pool, context, &scan)) &&
+ !(error = git_attr_assignment__parse(
+ repo, &attrs->pool, &rule->assigns, &scan)))
+ {
+ if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
+ /* TODO: warning if macro found in file below repo root */
+ error = git_attr_cache__insert_macro(repo, rule);
+ else
+ error = git_vector_insert(&attrs->rules, rule);
+ }
+
+ /* if the rule wasn't a pattern, on to the next */
+ if (error < 0) {
+ git_attr_rule__clear(rule); /* reset rule contents */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ } else {
+ rule = NULL; /* vector now "owns" the rule */
+ }
+ }
+
+ git_mutex_unlock(&attrs->lock);
+ git_attr_rule__free(rule);
+
+ return error;
+}
+
+uint32_t git_attr_file__name_hash(const char *name)
+{
+ uint32_t h = 5381;
+ int c;
+ assert(name);
+ while ((c = (int)*name++) != 0)
+ h = ((h << 5) + h) + c;
+ return h;
+}
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ git_attr_path *path,
+ const char *attr,
+ const char **value)
+{
+ size_t i;
+ git_attr_name name;
+ git_attr_rule *rule;
+
+ *value = NULL;
+
+ name.name = attr;
+ name.name_hash = git_attr_file__name_hash(attr);
+
+ git_attr_file__foreach_matching_rule(file, path, i, rule) {
+ size_t pos;
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
+ *value = ((git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos))->value;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int git_attr_file__load_standalone(git_attr_file **out, const char *path)
+{
+ int error;
+ git_attr_file *file;
+ git_buf content = GIT_BUF_INIT;
+
+ error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
+ if (error < 0)
+ return error;
+
+ error = git_attr_cache__alloc_file_entry(
+ &file->entry, NULL, path, &file->pool);
+ if (error < 0) {
+ git_attr_file__free(file);
+ return error;
+ }
+ /* because the cache entry is allocated from the file's own pool, we
+ * don't have to free it - freeing file+pool will free cache entry, too.
+ */
+
+ if (!(error = git_futils_readbuffer(&content, path))) {
+ error = git_attr_file__parse_buffer(NULL, file, content.ptr);
+ git_buf_free(&content);
+ }
+
+ if (error < 0)
+ git_attr_file__free(file);
+ else
+ *out = file;
+
+ return error;
+}
+
+bool git_attr_fnmatch__match(
+ git_attr_fnmatch *match,
+ git_attr_path *path)
+{
+ const char *relpath = path->path;
+ const char *filename;
+ int flags = 0;
+
+ /*
+ * If the rule was generated in a subdirectory, we must only
+ * use it for paths inside that directory. We can thus return
+ * a non-match if the prefixes don't match.
+ */
+ if (match->containing_dir) {
+ if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
+ if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
+ return 0;
+ } else {
+ if (git__prefixcmp(path->path, match->containing_dir))
+ return 0;
+ }
+
+ relpath += match->containing_dir_length;
+ }
+
+ if (match->flags & GIT_ATTR_FNMATCH_ICASE)
+ flags |= FNM_CASEFOLD;
+ if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
+ flags |= FNM_LEADING_DIR;
+
+ if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
+ filename = relpath;
+ flags |= FNM_PATHNAME;
+ } else {
+ filename = path->basename;
+
+ if (path->is_dir)
+ flags |= FNM_LEADING_DIR;
+ }
+
+ if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
+ bool samename;
+
+ /* for attribute checks or root ignore checks, fail match */
+ if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
+ path->basename == path->path)
+ return false;
+
+ flags |= FNM_LEADING_DIR;
+
+ /* fail match if this is a file with same name as ignored folder */
+ samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
+ !strcasecmp(match->pattern, relpath) :
+ !strcmp(match->pattern, relpath);
+
+ if (samename)
+ return false;
+
+ return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
+ }
+
+ /* if path is a directory prefix of a negated pattern, then match */
+ if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
+ size_t pathlen = strlen(relpath);
+ bool prefixed = (pathlen <= match->length) &&
+ ((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
+ !strncasecmp(match->pattern, relpath, pathlen) :
+ !strncmp(match->pattern, relpath, pathlen));
+
+ if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
+ return true;
+ }
+
+ return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
+}
+
+bool git_attr_rule__match(
+ git_attr_rule *rule,
+ git_attr_path *path)
+{
+ bool matched = git_attr_fnmatch__match(&rule->match, path);
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ matched = !matched;
+
+ return matched;
+}
+
+git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name)
+{
+ size_t pos;
+ git_attr_name key;
+ key.name = name;
+ key.name_hash = git_attr_file__name_hash(name);
+
+ if (git_vector_bsearch(&pos, &rule->assigns, &key))
+ return NULL;
+
+ return git_vector_get(&rule->assigns, pos);
+}
+
+int git_attr_path__init(
+ git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
+{
+ ssize_t root;
+
+ /* build full path as best we can */
+ git_buf_init(&info->full, 0);
+
+ if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
+ return -1;
+
+ info->path = info->full.ptr + root;
+
+ /* remove trailing slashes */
+ while (info->full.size > 0) {
+ if (info->full.ptr[info->full.size - 1] != '/')
+ break;
+ info->full.size--;
+ }
+ info->full.ptr[info->full.size] = '\0';
+
+ /* skip leading slashes in path */
+ while (*info->path == '/')
+ info->path++;
+
+ /* find trailing basename component */
+ info->basename = strrchr(info->path, '/');
+ if (info->basename)
+ info->basename++;
+ if (!info->basename || !*info->basename)
+ info->basename = info->path;
+
+ switch (dir_flag)
+ {
+ case GIT_DIR_FLAG_FALSE:
+ info->is_dir = 0;
+ break;
+
+ case GIT_DIR_FLAG_TRUE:
+ info->is_dir = 1;
+ break;
+
+ case GIT_DIR_FLAG_UNKNOWN:
+ default:
+ info->is_dir = (int)git_path_isdir(info->full.ptr);
+ break;
+ }
+
+ return 0;
+}
+
+void git_attr_path__free(git_attr_path *info)
+{
+ git_buf_free(&info->full);
+ info->path = NULL;
+ info->basename = NULL;
+}
+
+/*
+ * From gitattributes(5):
+ *
+ * Patterns have the following format:
+ *
+ * - A blank line matches no files, so it can serve as a separator for
+ * readability.
+ *
+ * - A line starting with # serves as a comment.
+ *
+ * - An optional prefix ! which negates the pattern; any matching file
+ * excluded by a previous pattern will become included again. If a negated
+ * pattern matches, this will override lower precedence patterns sources.
+ *
+ * - If the pattern ends with a slash, it is removed for the purpose of the
+ * following description, but it would only find a match with a directory. In
+ * other words, foo/ will match a directory foo and paths underneath it, but
+ * will not match a regular file or a symbolic link foo (this is consistent
+ * with the way how pathspec works in general in git).
+ *
+ * - If the pattern does not contain a slash /, git treats it as a shell glob
+ * pattern and checks for a match against the pathname without leading
+ * directories.
+ *
+ * - Otherwise, git treats the pattern as a shell glob suitable for consumption
+ * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
+ * not match a / in the pathname. For example, "Documentation/\*.html" matches
+ * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
+ * slash matches the beginning of the pathname; for example, "/\*.c" matches
+ * "cat-file.c" but not "mozilla-sha1/sha1.c".
+ */
+
+/*
+ * This will return 0 if the spec was filled out,
+ * GIT_ENOTFOUND if the fnmatch does not require matching, or
+ * another error code there was an actual problem.
+ */
+int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *context,
+ const char **base)
+{
+ const char *pattern, *scan;
+ int slash_count, allow_space;
+
+ assert(spec && base && *base);
+
+ if (parse_optimized_patterns(spec, pool, *base))
+ return 0;
+
+ spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
+ allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
+
+ pattern = *base;
+
+ while (git__isspace(*pattern)) pattern++;
+ if (!*pattern || *pattern == '#') {
+ *base = git__next_line(pattern);
+ return GIT_ENOTFOUND;
+ }
+
+ if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
+ if (strncmp(pattern, "[attr]", 6) == 0) {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
+ pattern += 6;
+ }
+ /* else a character range like [a-e]* which is accepted */
+ }
+
+ if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
+ spec->flags = spec->flags |
+ GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
+ pattern++;
+ }
+
+ slash_count = 0;
+ for (scan = pattern; *scan != '\0'; ++scan) {
+ /* scan until (non-escaped) white space */
+ if (git__isspace(*scan) && *(scan - 1) != '\\') {
+ if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r'))
+ break;
+ }
+
+ if (*scan == '/') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
+ slash_count++;
+ if (pattern == scan)
+ pattern++;
+ }
+ /* remember if we see an unescaped wildcard in pattern */
+ else if (git__iswildcard(*scan) &&
+ (scan == pattern || (*(scan - 1) != '\\')))
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
+ }
+
+ *base = scan;
+
+ if ((spec->length = scan - pattern) == 0)
+ return GIT_ENOTFOUND;
+
+ /*
+ * Remove one trailing \r in case this is a CRLF delimited
+ * file, in the case of Icon\r\r\n, we still leave the first
+ * \r there to match against.
+ */
+ if (pattern[spec->length - 1] == '\r')
+ if (--spec->length == 0)
+ return GIT_ENOTFOUND;
+
+ if (pattern[spec->length - 1] == '/') {
+ spec->length--;
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
+ if (--slash_count <= 0)
+ spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
+ }
+ if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
+ spec->length >= 2 &&
+ pattern[spec->length - 1] == '*' &&
+ pattern[spec->length - 2] == '/') {
+ spec->length -= 2;
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
+ /* leave FULLPATH match on, however */
+ }
+
+ if (context) {
+ char *slash = strrchr(context, '/');
+ size_t len;
+ if (slash) {
+ /* include the slash for easier matching */
+ len = slash - context + 1;
+ spec->containing_dir = git_pool_strndup(pool, context, len);
+ spec->containing_dir_length = len;
+ }
+ }
+
+ spec->pattern = git_pool_strndup(pool, pattern, spec->length);
+
+ if (!spec->pattern) {
+ *base = git__next_line(pattern);
+ return -1;
+ } else {
+ /* strip '\' that might have be used for internal whitespace */
+ spec->length = git__unescape(spec->pattern);
+ /* TODO: convert remaining '\' into '/' for POSIX ??? */
+ }
+
+ return 0;
+}
+
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern)
+{
+ if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
+ spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
+ spec->pattern = git_pool_strndup(pool, pattern, 1);
+ spec->length = 1;
+
+ return true;
+ }
+
+ return false;
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
+{
+ const git_attr_name *a = a_raw;
+ const git_attr_name *b = b_raw;
+
+ if (b->name_hash < a->name_hash)
+ return 1;
+ else if (b->name_hash > a->name_hash)
+ return -1;
+ else
+ return strcmp(b->name, a->name);
+}
+
+static void git_attr_assignment__free(git_attr_assignment *assign)
+{
+ /* name and value are stored in a git_pool associated with the
+ * git_attr_file, so they do not need to be freed here
+ */
+ assign->name = NULL;
+ assign->value = NULL;
+ git__free(assign);
+}
+
+static int merge_assignments(void **old_raw, void *new_raw)
+{
+ git_attr_assignment **old = (git_attr_assignment **)old_raw;
+ git_attr_assignment *new = (git_attr_assignment *)new_raw;
+
+ GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
+ *old = new;
+ return GIT_EEXISTS;
+}
+
+int git_attr_assignment__parse(
+ git_repository *repo,
+ git_pool *pool,
+ git_vector *assigns,
+ const char **base)
+{
+ int error;
+ const char *scan = *base;
+ git_attr_assignment *assign = NULL;
+
+ assert(assigns && !assigns->length);
+
+ git_vector_set_cmp(assigns, sort_by_hash_and_name);
+
+ while (*scan && *scan != '\n') {
+ const char *name_start, *value_start;
+
+ /* skip leading blanks */
+ while (git__isspace(*scan) && *scan != '\n') scan++;
+
+ /* allocate assign if needed */
+ if (!assign) {
+ assign = git__calloc(1, sizeof(git_attr_assignment));
+ GITERR_CHECK_ALLOC(assign);
+ GIT_REFCOUNT_INC(assign);
+ }
+
+ assign->name_hash = 5381;
+ assign->value = git_attr__true;
+
+ /* look for magic name prefixes */
+ if (*scan == '-') {
+ assign->value = git_attr__false;
+ scan++;
+ } else if (*scan == '!') {
+ assign->value = git_attr__unset; /* explicit unspecified state */
+ scan++;
+ } else if (*scan == '#') /* comment rest of line */
+ break;
+
+ /* find the name */
+ name_start = scan;
+ while (*scan && !git__isspace(*scan) && *scan != '=') {
+ assign->name_hash =
+ ((assign->name_hash << 5) + assign->name_hash) + *scan;
+ scan++;
+ }
+ if (scan == name_start) {
+ /* must have found lone prefix (" - ") or leading = ("=foo")
+ * or end of buffer -- advance until whitespace and continue
+ */
+ while (*scan && !git__isspace(*scan)) scan++;
+ continue;
+ }
+
+ /* allocate permanent storage for name */
+ assign->name = git_pool_strndup(pool, name_start, scan - name_start);
+ GITERR_CHECK_ALLOC(assign->name);
+
+ /* if there is an equals sign, find the value */
+ if (*scan == '=') {
+ for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
+
+ /* if we found a value, allocate permanent storage for it */
+ if (scan > value_start) {
+ assign->value = git_pool_strndup(pool, value_start, scan - value_start);
+ GITERR_CHECK_ALLOC(assign->value);
+ }
+ }
+
+ /* expand macros (if given a repo with a macro cache) */
+ if (repo != NULL && assign->value == git_attr__true) {
+ git_attr_rule *macro =
+ git_attr_cache__lookup_macro(repo, assign->name);
+
+ if (macro != NULL) {
+ unsigned int i;
+ git_attr_assignment *massign;
+
+ git_vector_foreach(¯o->assigns, i, massign) {
+ GIT_REFCOUNT_INC(massign);
+
+ error = git_vector_insert_sorted(
+ assigns, massign, &merge_assignments);
+ if (error < 0 && error != GIT_EEXISTS) {
+ git_attr_assignment__free(assign);
+ return error;
+ }
+ }
+ }
+ }
+
+ /* insert allocated assign into vector */
+ error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
+ if (error < 0 && error != GIT_EEXISTS)
+ return error;
+
+ /* clear assign since it is now "owned" by the vector */
+ assign = NULL;
+ }
+
+ if (assign != NULL)
+ git_attr_assignment__free(assign);
+
+ *base = git__next_line(scan);
+
+ return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
+}
+
+static void git_attr_rule__clear(git_attr_rule *rule)
+{
+ unsigned int i;
+ git_attr_assignment *assign;
+
+ if (!rule)
+ return;
+
+ if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
+ git_vector_foreach(&rule->assigns, i, assign)
+ GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
+ git_vector_free(&rule->assigns);
+ }
+
+ /* match.pattern is stored in a git_pool, so no need to free */
+ rule->match.pattern = NULL;
+ rule->match.length = 0;
+}
+
+void git_attr_rule__free(git_attr_rule *rule)
+{
+ git_attr_rule__clear(rule);
+ git__free(rule);
+}
+
+int git_attr_session__init(git_attr_session *session, git_repository *repo)
+{
+ assert(repo);
+
+ session->key = git_atomic_inc(&repo->attr_session_key);
+
+ return 0;
+}
+
+void git_attr_session__free(git_attr_session *session)
+{
+ if (!session)
+ return;
+
+ git_buf_free(&session->sysdir);
+ git_buf_free(&session->tmp);
+
+ memset(session, 0, sizeof(git_attr_session));
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attr_file_h__
+#define INCLUDE_attr_file_h__
+
+#include "git2/oid.h"
+#include "git2/attr.h"
+#include "vector.h"
+#include "pool.h"
+#include "buffer.h"
+#include "fileops.h"
+
+#define GIT_ATTR_FILE ".gitattributes"
+#define GIT_ATTR_FILE_INREPO "info/attributes"
+#define GIT_ATTR_FILE_SYSTEM "gitattributes"
+#define GIT_ATTR_FILE_XDG "attributes"
+
+#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
+#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
+#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
+#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
+#define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
+#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
+#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
+#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
+#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
+#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
+#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10)
+#define GIT_ATTR_FNMATCH_LEADINGDIR (1U << 11)
+#define GIT_ATTR_FNMATCH_NOLEADINGDIR (1U << 12)
+
+#define GIT_ATTR_FNMATCH__INCOMING \
+ (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \
+ GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR)
+
+typedef enum {
+ GIT_ATTR_FILE__IN_MEMORY = 0,
+ GIT_ATTR_FILE__FROM_FILE = 1,
+ GIT_ATTR_FILE__FROM_INDEX = 2,
+
+ GIT_ATTR_FILE_NUM_SOURCES = 3
+} git_attr_file_source;
+
+extern const char *git_attr__true;
+extern const char *git_attr__false;
+extern const char *git_attr__unset;
+
+typedef struct {
+ char *pattern;
+ size_t length;
+ char *containing_dir;
+ size_t containing_dir_length;
+ unsigned int flags;
+} git_attr_fnmatch;
+
+typedef struct {
+ git_attr_fnmatch match;
+ git_vector assigns; /* vector of <git_attr_assignment*> */
+} git_attr_rule;
+
+typedef struct {
+ git_refcount unused;
+ const char *name;
+ uint32_t name_hash;
+} git_attr_name;
+
+typedef struct {
+ git_refcount rc; /* for macros */
+ char *name;
+ uint32_t name_hash;
+ const char *value;
+} git_attr_assignment;
+
+typedef struct git_attr_file_entry git_attr_file_entry;
+
+typedef struct {
+ git_refcount rc;
+ git_mutex lock;
+ git_attr_file_entry *entry;
+ git_attr_file_source source;
+ git_vector rules; /* vector of <rule*> or <fnmatch*> */
+ git_pool pool;
+ unsigned int nonexistent:1;
+ int session_key;
+ union {
+ git_oid oid;
+ git_futils_filestamp stamp;
+ } cache_data;
+} git_attr_file;
+
+struct git_attr_file_entry {
+ git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES];
+ const char *path; /* points into fullpath */
+ char fullpath[GIT_FLEX_ARRAY];
+};
+
+typedef struct {
+ git_buf full;
+ char *path;
+ char *basename;
+ int is_dir;
+} git_attr_path;
+
+/* A git_attr_session can provide an "instance" of reading, to prevent cache
+ * invalidation during a single operation instance (like checkout).
+ */
+
+typedef struct {
+ int key;
+ unsigned int init_setup:1,
+ init_sysdir:1;
+ git_buf sysdir;
+ git_buf tmp;
+} git_attr_session;
+
+extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo);
+extern void git_attr_session__free(git_attr_session *session);
+
+extern int git_attr_get_many_with_session(
+ const char **values_out,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ uint32_t flags,
+ const char *path,
+ size_t num_attr,
+ const char **names);
+
+typedef int (*git_attr_file_parser)(
+ git_repository *repo,
+ git_attr_file *file,
+ const char *data);
+
+/*
+ * git_attr_file API
+ */
+
+int git_attr_file__new(
+ git_attr_file **out,
+ git_attr_file_entry *entry,
+ git_attr_file_source source);
+
+void git_attr_file__free(git_attr_file *file);
+
+int git_attr_file__load(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_entry *ce,
+ git_attr_file_source source,
+ git_attr_file_parser parser);
+
+int git_attr_file__load_standalone(
+ git_attr_file **out, const char *path);
+
+int git_attr_file__out_of_date(
+ git_repository *repo, git_attr_session *session, git_attr_file *file);
+
+int git_attr_file__parse_buffer(
+ git_repository *repo, git_attr_file *attrs, const char *data);
+
+int git_attr_file__clear_rules(
+ git_attr_file *file, bool need_lock);
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ git_attr_path *path,
+ const char *attr,
+ const char **value);
+
+/* loop over rules in file from bottom to top */
+#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \
+ git_vector_rforeach(&(file)->rules, (iter), (rule)) \
+ if (git_attr_rule__match((rule), (path)))
+
+uint32_t git_attr_file__name_hash(const char *name);
+
+
+/*
+ * other utilities
+ */
+
+extern int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *source,
+ const char **base);
+
+extern bool git_attr_fnmatch__match(
+ git_attr_fnmatch *rule,
+ git_attr_path *path);
+
+extern void git_attr_rule__free(git_attr_rule *rule);
+
+extern bool git_attr_rule__match(
+ git_attr_rule *rule,
+ git_attr_path *path);
+
+extern git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name);
+
+typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
+
+extern int git_attr_path__init(
+ git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
+
+extern void git_attr_path__free(git_attr_path *info);
+
+extern int git_attr_assignment__parse(
+ git_repository *repo, /* needed to expand macros */
+ git_pool *pool,
+ git_vector *assigns,
+ const char **scan);
+
+#endif
--- /dev/null
+#include "common.h"
+#include "repository.h"
+#include "attr_file.h"
+#include "config.h"
+#include "sysdir.h"
+#include "ignore.h"
+
+GIT__USE_STRMAP
+
+GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
+{
+ GIT_UNUSED(cache); /* avoid warning if threading is off */
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to get attr cache lock");
+ return -1;
+ }
+ return 0;
+}
+
+GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
+{
+ GIT_UNUSED(cache); /* avoid warning if threading is off */
+ git_mutex_unlock(&cache->lock);
+}
+
+GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
+ git_attr_cache *cache, const char *path)
+{
+ khiter_t pos = git_strmap_lookup_index(cache->files, path);
+
+ if (git_strmap_valid_index(cache->files, pos))
+ return git_strmap_value_at(cache->files, pos);
+ else
+ return NULL;
+}
+
+int git_attr_cache__alloc_file_entry(
+ git_attr_file_entry **out,
+ const char *base,
+ const char *path,
+ git_pool *pool)
+{
+ size_t baselen = 0, pathlen = strlen(path);
+ size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
+ git_attr_file_entry *ce;
+
+ if (base != NULL && git_path_root(path) < 0) {
+ baselen = strlen(base);
+ cachesize += baselen;
+
+ if (baselen && base[baselen - 1] != '/')
+ cachesize++;
+ }
+
+ ce = git_pool_mallocz(pool, (uint32_t)cachesize);
+ GITERR_CHECK_ALLOC(ce);
+
+ if (baselen) {
+ memcpy(ce->fullpath, base, baselen);
+
+ if (base[baselen - 1] != '/')
+ ce->fullpath[baselen++] = '/';
+ }
+ memcpy(&ce->fullpath[baselen], path, pathlen);
+
+ ce->path = &ce->fullpath[baselen];
+ *out = ce;
+
+ return 0;
+}
+
+/* call with attrcache locked */
+static int attr_cache_make_entry(
+ git_attr_file_entry **out, git_repository *repo, const char *path)
+{
+ int error = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+
+ error = git_attr_cache__alloc_file_entry(
+ &entry, git_repository_workdir(repo), path, &cache->pool);
+
+ if (!error) {
+ git_strmap_insert(cache->files, entry->path, entry, error);
+ if (error > 0)
+ error = 0;
+ }
+
+ *out = entry;
+ return error;
+}
+
+/* insert entry or replace existing if we raced with another thread */
+static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
+{
+ git_attr_file_entry *entry;
+ git_attr_file *old;
+
+ if (attr_cache_lock(cache) < 0)
+ return -1;
+
+ entry = attr_cache_lookup_entry(cache, file->entry->path);
+
+ GIT_REFCOUNT_OWN(file, entry);
+ GIT_REFCOUNT_INC(file);
+
+ old = git__compare_and_swap(
+ &entry->file[file->source], entry->file[file->source], file);
+
+ if (old) {
+ GIT_REFCOUNT_OWN(old, NULL);
+ git_attr_file__free(old);
+ }
+
+ attr_cache_unlock(cache);
+ return 0;
+}
+
+static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
+{
+ int error = 0;
+ git_attr_file_entry *entry;
+
+ if (!file)
+ return 0;
+ if ((error = attr_cache_lock(cache)) < 0)
+ return error;
+
+ if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
+ file = git__compare_and_swap(&entry->file[file->source], file, NULL);
+
+ attr_cache_unlock(cache);
+
+ if (file) {
+ GIT_REFCOUNT_OWN(file, NULL);
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+/* Look up cache entry and file.
+ * - If entry is not present, create it while the cache is locked.
+ * - If file is present, increment refcount before returning it, so the
+ * cache can be unlocked and it won't go away.
+ */
+static int attr_cache_lookup(
+ git_attr_file **out_file,
+ git_attr_file_entry **out_entry,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ const char *wd = git_repository_workdir(repo), *relfile;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+ git_attr_file *file = NULL;
+
+ /* join base and path as needed */
+ if (base != NULL && git_path_root(filename) < 0) {
+ git_buf *p = attr_session ? &attr_session->tmp : &path;
+
+ if (git_buf_joinpath(p, base, filename) < 0)
+ return -1;
+
+ filename = p->ptr;
+ }
+
+ relfile = filename;
+ if (wd && !git__prefixcmp(relfile, wd))
+ relfile += strlen(wd);
+
+ /* check cache for existing entry */
+ if ((error = attr_cache_lock(cache)) < 0)
+ goto cleanup;
+
+ entry = attr_cache_lookup_entry(cache, relfile);
+ if (!entry)
+ error = attr_cache_make_entry(&entry, repo, relfile);
+ else if (entry->file[source] != NULL) {
+ file = entry->file[source];
+ GIT_REFCOUNT_INC(file);
+ }
+
+ attr_cache_unlock(cache);
+
+cleanup:
+ *out_file = file;
+ *out_entry = entry;
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_attr_cache__get(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename,
+ git_attr_file_parser parser)
+{
+ int error = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+ git_attr_file *file = NULL, *updated = NULL;
+
+ if ((error = attr_cache_lookup(
+ &file, &entry, repo, attr_session, source, base, filename)) < 0)
+ return error;
+
+ /* load file if we don't have one or if existing one is out of date */
+ if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
+ error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser);
+
+ /* if we loaded the file, insert into and/or update cache */
+ if (updated) {
+ if ((error = attr_cache_upsert(cache, updated)) < 0)
+ git_attr_file__free(updated);
+ else {
+ git_attr_file__free(file); /* offset incref from lookup */
+ file = updated;
+ }
+ }
+
+ /* if file could not be loaded */
+ if (error < 0) {
+ /* remove existing entry */
+ if (file) {
+ attr_cache_remove(cache, file);
+ git_attr_file__free(file); /* offset incref from lookup */
+ file = NULL;
+ }
+ /* no error if file simply doesn't exist */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+ }
+
+ *out = file;
+ return error;
+}
+
+bool git_attr_cache__is_cached(
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *filename)
+{
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_strmap *files;
+ khiter_t pos;
+ git_attr_file_entry *entry;
+
+ if (!cache || !(files = cache->files))
+ return false;
+
+ pos = git_strmap_lookup_index(files, filename);
+ if (!git_strmap_valid_index(files, pos))
+ return false;
+
+ entry = git_strmap_value_at(files, pos);
+
+ return entry && (entry->file[source] != NULL);
+}
+
+
+static int attr_cache__lookup_path(
+ char **out, git_config *cfg, const char *key, const char *fallback)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+ git_config_entry *entry = NULL;
+
+ *out = NULL;
+
+ if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
+ return error;
+
+ if (entry) {
+ const char *cfgval = entry->value;
+
+ /* expand leading ~/ as needed */
+ if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
+ !git_sysdir_find_global_file(&buf, &cfgval[2]))
+ *out = git_buf_detach(&buf);
+ else if (cfgval)
+ *out = git__strdup(cfgval);
+ }
+ else if (!git_sysdir_find_xdg_file(&buf, fallback))
+ *out = git_buf_detach(&buf);
+
+ git_config_entry_free(entry);
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static void attr_cache__free(git_attr_cache *cache)
+{
+ bool unlock;
+
+ if (!cache)
+ return;
+
+ unlock = (git_mutex_lock(&cache->lock) == 0);
+
+ if (cache->files != NULL) {
+ git_attr_file_entry *entry;
+ git_attr_file *file;
+ int i;
+
+ git_strmap_foreach_value(cache->files, entry, {
+ for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
+ if ((file = git__swap(entry->file[i], NULL)) != NULL) {
+ GIT_REFCOUNT_OWN(file, NULL);
+ git_attr_file__free(file);
+ }
+ }
+ });
+ git_strmap_free(cache->files);
+ }
+
+ if (cache->macros != NULL) {
+ git_attr_rule *rule;
+
+ git_strmap_foreach_value(cache->macros, rule, {
+ git_attr_rule__free(rule);
+ });
+ git_strmap_free(cache->macros);
+ }
+
+ git_pool_clear(&cache->pool);
+
+ git__free(cache->cfg_attr_file);
+ cache->cfg_attr_file = NULL;
+
+ git__free(cache->cfg_excl_file);
+ cache->cfg_excl_file = NULL;
+
+ if (unlock)
+ git_mutex_unlock(&cache->lock);
+ git_mutex_free(&cache->lock);
+
+ git__free(cache);
+}
+
+int git_attr_cache__do_init(git_repository *repo)
+{
+ int ret = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_config *cfg = NULL;
+
+ if (cache)
+ return 0;
+
+ cache = git__calloc(1, sizeof(git_attr_cache));
+ GITERR_CHECK_ALLOC(cache);
+
+ /* set up lock */
+ if (git_mutex_init(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
+ git__free(cache);
+ return -1;
+ }
+
+ if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
+ goto cancel;
+
+ /* cache config settings for attributes and ignores */
+ ret = attr_cache__lookup_path(
+ &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
+ if (ret < 0)
+ goto cancel;
+
+ ret = attr_cache__lookup_path(
+ &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
+ if (ret < 0)
+ goto cancel;
+
+ /* allocate hashtable for attribute and ignore file contents,
+ * hashtable for attribute macros, and string pool
+ */
+ if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
+ (ret = git_strmap_alloc(&cache->macros)) < 0)
+ goto cancel;
+
+ git_pool_init(&cache->pool, 1);
+
+ cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
+ if (cache)
+ goto cancel; /* raced with another thread, free this but no error */
+
+ git_config_free(cfg);
+
+ /* insert default macros */
+ return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
+
+cancel:
+ attr_cache__free(cache);
+ git_config_free(cfg);
+ return ret;
+}
+
+void git_attr_cache_flush(git_repository *repo)
+{
+ git_attr_cache *cache;
+
+ /* this could be done less expensively, but for now, we'll just free
+ * the entire attrcache and let the next use reinitialize it...
+ */
+ if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
+ attr_cache__free(cache);
+}
+
+int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
+{
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_strmap *macros = cache->macros;
+ int error;
+
+ /* TODO: generate warning log if (macro->assigns.length == 0) */
+ if (macro->assigns.length == 0)
+ return 0;
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to get attr cache lock");
+ error = -1;
+ } else {
+ git_strmap_insert(macros, macro->match.pattern, macro, error);
+ git_mutex_unlock(&cache->lock);
+ }
+
+ return (error < 0) ? -1 : 0;
+}
+
+git_attr_rule *git_attr_cache__lookup_macro(
+ git_repository *repo, const char *name)
+{
+ git_strmap *macros = git_repository_attr_cache(repo)->macros;
+ khiter_t pos;
+
+ pos = git_strmap_lookup_index(macros, name);
+
+ if (!git_strmap_valid_index(macros, pos))
+ return NULL;
+
+ return (git_attr_rule *)git_strmap_value_at(macros, pos);
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attrcache_h__
+#define INCLUDE_attrcache_h__
+
+#include "attr_file.h"
+#include "strmap.h"
+
+#define GIT_ATTR_CONFIG "core.attributesfile"
+#define GIT_IGNORE_CONFIG "core.excludesfile"
+
+typedef struct {
+ char *cfg_attr_file; /* cached value of core.attributesfile */
+ char *cfg_excl_file; /* cached value of core.excludesfile */
+ git_strmap *files; /* hash path to git_attr_cache_entry records */
+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */
+ git_mutex lock;
+ git_pool pool;
+} git_attr_cache;
+
+extern int git_attr_cache__do_init(git_repository *repo);
+
+#define git_attr_cache__init(REPO) \
+ (git_repository_attr_cache(REPO) ? 0 : git_attr_cache__do_init(REPO))
+
+/* get file - loading and reload as needed */
+extern int git_attr_cache__get(
+ git_attr_file **file,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename,
+ git_attr_file_parser parser);
+
+extern bool git_attr_cache__is_cached(
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *path);
+
+extern int git_attr_cache__alloc_file_entry(
+ git_attr_file_entry **out,
+ const char *base,
+ const char *path,
+ git_pool *pool);
+
+extern int git_attr_cache__insert_macro(
+ git_repository *repo, git_attr_rule *macro);
+
+extern git_attr_rule *git_attr_cache__lookup_macro(
+ git_repository *repo, const char *name);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_bitvec_h__
+#define INCLUDE_bitvec_h__
+
+#include "common.h"
+
+/*
+ * This is a silly little fixed length bit vector type that will store
+ * vectors of 64 bits or less directly in the structure and allocate
+ * memory for vectors longer than 64 bits. You can use the two versions
+ * transparently through the API and avoid heap allocation completely when
+ * using a short bit vector as a result.
+ */
+typedef struct {
+ size_t length;
+ union {
+ uint64_t *words;
+ uint64_t bits;
+ } u;
+} git_bitvec;
+
+GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
+{
+ memset(bv, 0x0, sizeof(*bv));
+
+ if (capacity >= 64) {
+ bv->length = (capacity / 64) + 1;
+ bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
+ if (!bv->u.words)
+ return -1;
+ }
+
+ return 0;
+}
+
+#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
+#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
+
+GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ uint64_t mask = GIT_BITVEC_MASK(bit);
+
+ if (on)
+ *word |= mask;
+ else
+ *word &= ~mask;
+}
+
+GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ return (*word & GIT_BITVEC_MASK(bit)) != 0;
+}
+
+GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
+{
+ if (!bv->length)
+ bv->u.bits = 0;
+ else
+ memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
+}
+
+GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
+{
+ if (bv->length)
+ git__free(bv->u.words);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "blame.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "git2/revwalk.h"
+#include "git2/tree.h"
+#include "git2/diff.h"
+#include "git2/blob.h"
+#include "git2/signature.h"
+#include "util.h"
+#include "repository.h"
+#include "blame_git.h"
+
+
+static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
+{
+ git_blame_hunk *hunk = (git_blame_hunk*)entry;
+
+ size_t lineno = *(size_t*)key;
+ size_t lines_in_hunk = hunk->lines_in_hunk;
+ size_t final_start_line_number = hunk->final_start_line_number;
+
+ if (lineno < final_start_line_number)
+ return -1;
+ if (lineno >= final_start_line_number + lines_in_hunk)
+ return 1;
+ return 0;
+}
+
+static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
+static int hunk_cmp(const void *_a, const void *_b)
+{
+ git_blame_hunk *a = (git_blame_hunk*)_a,
+ *b = (git_blame_hunk*)_b;
+
+ return a->final_start_line_number - b->final_start_line_number;
+}
+
+static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
+{
+ return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1);
+}
+
+static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
+{
+ return line <= hunk->final_start_line_number;
+}
+
+static git_blame_hunk* new_hunk(
+ size_t start,
+ size_t lines,
+ size_t orig_start,
+ const char *path)
+{
+ git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
+ if (!hunk) return NULL;
+
+ hunk->lines_in_hunk = lines;
+ hunk->final_start_line_number = start;
+ hunk->orig_start_line_number = orig_start;
+ hunk->orig_path = path ? git__strdup(path) : NULL;
+
+ return hunk;
+}
+
+static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
+{
+ git_blame_hunk *newhunk = new_hunk(
+ hunk->final_start_line_number,
+ hunk->lines_in_hunk,
+ hunk->orig_start_line_number,
+ hunk->orig_path);
+
+ if (!newhunk)
+ return NULL;
+
+ git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
+ git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
+ newhunk->boundary = hunk->boundary;
+ git_signature_dup(&newhunk->final_signature, hunk->final_signature);
+ git_signature_dup(&newhunk->orig_signature, hunk->orig_signature);
+ return newhunk;
+}
+
+static void free_hunk(git_blame_hunk *hunk)
+{
+ git__free((void*)hunk->orig_path);
+ git_signature_free(hunk->final_signature);
+ git_signature_free(hunk->orig_signature);
+ git__free(hunk);
+}
+
+/* Starting with the hunk that includes start_line, shift all following hunks'
+ * final_start_line by shift_by lines */
+static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
+{
+ size_t i;
+
+ if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) {
+ for (; i < v->length; i++) {
+ git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
+ hunk->final_start_line_number += shift_by;
+ }
+ }
+}
+
+git_blame* git_blame__alloc(
+ git_repository *repo,
+ git_blame_options opts,
+ const char *path)
+{
+ git_blame *gbr = git__calloc(1, sizeof(git_blame));
+ if (!gbr)
+ return NULL;
+
+ gbr->repository = repo;
+ gbr->options = opts;
+
+ if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 ||
+ git_vector_init(&gbr->paths, 8, paths_cmp) < 0 ||
+ (gbr->path = git__strdup(path)) == NULL ||
+ git_vector_insert(&gbr->paths, git__strdup(path)) < 0)
+ {
+ git_blame_free(gbr);
+ return NULL;
+ }
+
+ return gbr;
+}
+
+void git_blame_free(git_blame *blame)
+{
+ size_t i;
+ git_blame_hunk *hunk;
+
+ if (!blame) return;
+
+ git_vector_foreach(&blame->hunks, i, hunk)
+ free_hunk(hunk);
+ git_vector_free(&blame->hunks);
+
+ git_vector_free_deep(&blame->paths);
+
+ git_array_clear(blame->line_index);
+
+ git__free(blame->path);
+ git_blob_free(blame->final_blob);
+ git__free(blame);
+}
+
+uint32_t git_blame_get_hunk_count(git_blame *blame)
+{
+ assert(blame);
+ return (uint32_t)blame->hunks.length;
+}
+
+const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
+{
+ assert(blame);
+ return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
+}
+
+const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno)
+{
+ size_t i, new_lineno = lineno;
+ assert(blame);
+
+ if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) {
+ return git_blame_get_hunk_byindex(blame, (uint32_t)i);
+ }
+
+ return NULL;
+}
+
+static int normalize_options(
+ git_blame_options *out,
+ const git_blame_options *in,
+ git_repository *repo)
+{
+ git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
+ if (!in) in = &dummy;
+
+ memcpy(out, in, sizeof(git_blame_options));
+
+ /* No newest_commit => HEAD */
+ if (git_oid_iszero(&out->newest_commit)) {
+ if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) {
+ return -1;
+ }
+ }
+
+ /* min_line 0 really means 1 */
+ if (!out->min_line) out->min_line = 1;
+ /* max_line 0 really means N, but we don't know N yet */
+
+ /* Fix up option implications */
+ if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+ if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+ if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE;
+
+ return 0;
+}
+
+static git_blame_hunk *split_hunk_in_vector(
+ git_vector *vec,
+ git_blame_hunk *hunk,
+ size_t rel_line,
+ bool return_new)
+{
+ size_t new_line_count;
+ git_blame_hunk *nh;
+
+ /* Don't split if already at a boundary */
+ if (rel_line <= 0 ||
+ rel_line >= hunk->lines_in_hunk)
+ {
+ return hunk;
+ }
+
+ new_line_count = hunk->lines_in_hunk - rel_line;
+ nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count,
+ hunk->orig_start_line_number + rel_line, hunk->orig_path);
+
+ if (!nh)
+ return NULL;
+
+ git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
+ git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
+
+ /* Adjust hunk that was split */
+ hunk->lines_in_hunk -= new_line_count;
+ git_vector_insert_sorted(vec, nh, NULL);
+ {
+ git_blame_hunk *ret = return_new ? nh : hunk;
+ return ret;
+ }
+}
+
+/*
+ * Construct a list of char indices for where lines begin
+ * Adapted from core git:
+ * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
+ */
+static int index_blob_lines(git_blame *blame)
+{
+ const char *buf = blame->final_buf;
+ git_off_t len = blame->final_buf_size;
+ int num = 0, incomplete = 0, bol = 1;
+ size_t *i;
+
+ if (len && buf[len-1] != '\n')
+ incomplete++; /* incomplete line at the end */
+ while (len--) {
+ if (bol) {
+ i = git_array_alloc(blame->line_index);
+ GITERR_CHECK_ALLOC(i);
+ *i = buf - blame->final_buf;
+ bol = 0;
+ }
+ if (*buf++ == '\n') {
+ num++;
+ bol = 1;
+ }
+ }
+ i = git_array_alloc(blame->line_index);
+ GITERR_CHECK_ALLOC(i);
+ *i = buf - blame->final_buf;
+ blame->num_lines = num + incomplete;
+ return blame->num_lines;
+}
+
+static git_blame_hunk* hunk_from_entry(git_blame__entry *e)
+{
+ git_blame_hunk *h = new_hunk(
+ e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
+
+ if (!h)
+ return NULL;
+
+ git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
+ git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit));
+ git_signature_dup(&h->final_signature, git_commit_author(e->suspect->commit));
+ git_signature_dup(&h->orig_signature, git_commit_author(e->suspect->commit));
+ h->boundary = e->is_boundary ? 1 : 0;
+ return h;
+}
+
+static int load_blob(git_blame *blame)
+{
+ int error;
+
+ if (blame->final_blob) return 0;
+
+ error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit);
+ if (error < 0)
+ goto cleanup;
+ error = git_object_lookup_bypath((git_object**)&blame->final_blob,
+ (git_object*)blame->final, blame->path, GIT_OBJ_BLOB);
+
+cleanup:
+ return error;
+}
+
+static int blame_internal(git_blame *blame)
+{
+ int error;
+ git_blame__entry *ent = NULL;
+ git_blame__origin *o;
+
+ if ((error = load_blob(blame)) < 0 ||
+ (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0)
+ goto cleanup;
+ blame->final_buf = git_blob_rawcontent(blame->final_blob);
+ blame->final_buf_size = git_blob_rawsize(blame->final_blob);
+
+ ent = git__calloc(1, sizeof(git_blame__entry));
+ GITERR_CHECK_ALLOC(ent);
+
+ ent->num_lines = index_blob_lines(blame);
+ ent->lno = blame->options.min_line - 1;
+ ent->num_lines = ent->num_lines - blame->options.min_line + 1;
+ if (blame->options.max_line > 0)
+ ent->num_lines = blame->options.max_line - blame->options.min_line + 1;
+ ent->s_lno = ent->lno;
+ ent->suspect = o;
+
+ blame->ent = ent;
+
+ error = git_blame__like_git(blame, blame->options.flags);
+
+cleanup:
+ for (ent = blame->ent; ent; ) {
+ git_blame__entry *e = ent->next;
+ git_blame_hunk *h = hunk_from_entry(ent);
+
+ git_vector_insert(&blame->hunks, h);
+
+ git_blame__free_entry(ent);
+ ent = e;
+ }
+
+ return error;
+}
+
+/*******************************************************************************
+ * File blaming
+ ******************************************************************************/
+
+int git_blame_file(
+ git_blame **out,
+ git_repository *repo,
+ const char *path,
+ git_blame_options *options)
+{
+ int error = -1;
+ git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
+ git_blame *blame = NULL;
+
+ assert(out && repo && path);
+ if ((error = normalize_options(&normOptions, options, repo)) < 0)
+ goto on_error;
+
+ blame = git_blame__alloc(repo, normOptions, path);
+ GITERR_CHECK_ALLOC(blame);
+
+ if ((error = load_blob(blame)) < 0)
+ goto on_error;
+
+ if ((error = blame_internal(blame)) < 0)
+ goto on_error;
+
+ *out = blame;
+ return 0;
+
+on_error:
+ git_blame_free(blame);
+ return error;
+}
+
+/*******************************************************************************
+ * Buffer blaming
+ *******************************************************************************/
+
+static bool hunk_is_bufferblame(git_blame_hunk *hunk)
+{
+ return git_oid_iszero(&hunk->final_commit_id);
+}
+
+static int buffer_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *payload)
+{
+ git_blame *blame = (git_blame*)payload;
+ uint32_t wedge_line;
+
+ GIT_UNUSED(delta);
+
+ wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
+ blame->current_diff_line = wedge_line;
+
+ blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
+ if (!blame->current_hunk) {
+ /* Line added at the end of the file */
+ blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path);
+ GITERR_CHECK_ALLOC(blame->current_hunk);
+
+ git_vector_insert(&blame->hunks, blame->current_hunk);
+ } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
+ /* If this hunk doesn't start between existing hunks, split a hunk up so it does */
+ blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
+ wedge_line - blame->current_hunk->orig_start_line_number, true);
+ GITERR_CHECK_ALLOC(blame->current_hunk);
+ }
+
+ return 0;
+}
+
+static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
+static int buffer_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ git_blame *blame = (git_blame*)payload;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(hunk);
+ GIT_UNUSED(line);
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION) {
+ if (hunk_is_bufferblame(blame->current_hunk) &&
+ hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
+ /* Append to the current buffer-blame hunk */
+ blame->current_hunk->lines_in_hunk++;
+ shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
+ } else {
+ /* Create a new buffer-blame hunk with this line */
+ shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
+ blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path);
+ GITERR_CHECK_ALLOC(blame->current_hunk);
+
+ git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
+ }
+ blame->current_diff_line++;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_DELETION) {
+ /* Trim the line from the current hunk; remove it if it's now empty */
+ size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
+
+ if (--(blame->current_hunk->lines_in_hunk) == 0) {
+ size_t i;
+ shift_base--;
+ if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
+ git_vector_remove(&blame->hunks, i);
+ free_hunk(blame->current_hunk);
+ blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i);
+ }
+ }
+ shift_hunks_by(&blame->hunks, shift_base, -1);
+ }
+ return 0;
+}
+
+int git_blame_buffer(
+ git_blame **out,
+ git_blame *reference,
+ const char *buffer,
+ size_t buffer_len)
+{
+ git_blame *blame;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ size_t i;
+ git_blame_hunk *hunk;
+
+ diffopts.context_lines = 0;
+
+ assert(out && reference && buffer && buffer_len);
+
+ blame = git_blame__alloc(reference->repository, reference->options, reference->path);
+ GITERR_CHECK_ALLOC(blame);
+
+ /* Duplicate all of the hunk structures in the reference blame */
+ git_vector_foreach(&reference->hunks, i, hunk) {
+ git_blame_hunk *h = dup_hunk(hunk);
+ GITERR_CHECK_ALLOC(h);
+
+ git_vector_insert(&blame->hunks, h);
+ }
+
+ /* Diff to the reference blob */
+ git_diff_blob_to_buffer(reference->final_blob, blame->path,
+ buffer, buffer_len, blame->path, &diffopts,
+ NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame);
+
+ *out = blame;
+ return 0;
+}
+
+int git_blame_init_options(git_blame_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+#ifndef INCLUDE_blame_h__
+#define INCLUDE_blame_h__
+
+#include "git2/blame.h"
+#include "common.h"
+#include "vector.h"
+#include "diff.h"
+#include "array.h"
+#include "git2/oid.h"
+
+/*
+ * One blob in a commit that is being suspected
+ */
+typedef struct git_blame__origin {
+ int refcnt;
+ struct git_blame__origin *previous;
+ git_commit *commit;
+ git_blob *blob;
+ char path[GIT_FLEX_ARRAY];
+} git_blame__origin;
+
+/*
+ * Each group of lines is described by a git_blame__entry; it can be split
+ * as we pass blame to the parents. They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
+typedef struct git_blame__entry {
+ struct git_blame__entry *prev;
+ struct git_blame__entry *next;
+
+ /* the first line of this group in the final image;
+ * internally all line numbers are 0 based.
+ */
+ size_t lno;
+
+ /* how many lines this group has */
+ size_t num_lines;
+
+ /* the commit that introduced this group into the final image */
+ git_blame__origin *suspect;
+
+ /* true if the suspect is truly guilty; false while we have not
+ * checked if the group came from one of its parents.
+ */
+ bool guilty;
+
+ /* true if the entry has been scanned for copies in the current parent
+ */
+ bool scanned;
+
+ /* the line number of the first line of this group in the
+ * suspect's file; internally all line numbers are 0 based.
+ */
+ size_t s_lno;
+
+ /* how significant this entry is -- cached to avoid
+ * scanning the lines over and over.
+ */
+ unsigned score;
+
+ /* Whether this entry has been tracked to a boundary commit.
+ */
+ bool is_boundary;
+} git_blame__entry;
+
+struct git_blame {
+ char *path;
+ git_repository *repository;
+ git_blame_options options;
+
+ git_vector hunks;
+ git_vector paths;
+
+ git_blob *final_blob;
+ git_array_t(size_t) line_index;
+
+ size_t current_diff_line;
+ git_blame_hunk *current_hunk;
+
+ /* Scoreboard fields */
+ git_commit *final;
+ git_blame__entry *ent;
+ int num_lines;
+ const char *final_buf;
+ git_off_t final_buf_size;
+};
+
+git_blame *git_blame__alloc(
+ git_repository *repo,
+ git_blame_options opts,
+ const char *path);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "blame_git.h"
+#include "commit.h"
+#include "blob.h"
+#include "xdiff/xinclude.h"
+#include "diff_xdiff.h"
+
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
+static git_blame__origin *origin_incref(git_blame__origin *o)
+{
+ if (o)
+ o->refcnt++;
+ return o;
+}
+
+static void origin_decref(git_blame__origin *o)
+{
+ if (o && --o->refcnt <= 0) {
+ if (o->previous)
+ origin_decref(o->previous);
+ git_blob_free(o->blob);
+ git_commit_free(o->commit);
+ git__free(o);
+ }
+}
+
+/* Given a commit and a path in it, create a new origin structure. */
+static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
+{
+ git_blame__origin *o;
+ git_object *blob;
+ size_t path_len = strlen(path), alloc_len;
+ int error = 0;
+
+ if ((error = git_object_lookup_bypath(&blob, (git_object*)commit,
+ path, GIT_OBJ_BLOB)) < 0)
+ return error;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+ o = git__calloc(1, alloc_len);
+ GITERR_CHECK_ALLOC(o);
+
+ o->commit = commit;
+ o->blob = (git_blob *) blob;
+ o->refcnt = 1;
+ strcpy(o->path, path);
+
+ *out = o;
+
+ return 0;
+}
+
+/* Locate an existing origin or create a new one. */
+int git_blame__get_origin(
+ git_blame__origin **out,
+ git_blame *blame,
+ git_commit *commit,
+ const char *path)
+{
+ git_blame__entry *e;
+
+ for (e = blame->ent; e; e = e->next) {
+ if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) {
+ *out = origin_incref(e->suspect);
+ }
+ }
+ return make_origin(out, commit, path);
+}
+
+typedef struct blame_chunk_cb_data {
+ git_blame *blame;
+ git_blame__origin *target;
+ git_blame__origin *parent;
+ long tlno;
+ long plno;
+}blame_chunk_cb_data;
+
+static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
+{
+ if (a == b)
+ return true;
+ if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
+ return false;
+ return 0 == strcmp(a->path, b->path);
+}
+
+/* find the line number of the last line the target is suspected for */
+static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target)
+{
+ git_blame__entry *e;
+ size_t last_in_target = 0;
+ bool found = false;
+
+ *out = 0;
+
+ for (e=blame->ent; e; e=e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (last_in_target < e->s_lno + e->num_lines) {
+ found = true;
+ last_in_target = e->s_lno + e->num_lines;
+ }
+ }
+
+ *out = last_in_target;
+ return found;
+}
+
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range. it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------> (entirely within)
+ * <------------> (extends past)
+ * <------------> (starts before)
+ * <------------------> (entirely encloses)
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
+static void split_overlap(git_blame__entry *split, git_blame__entry *e,
+ size_t tlno, size_t plno, size_t same, git_blame__origin *parent)
+{
+ size_t chunk_end_lno;
+
+ if (e->s_lno < tlno) {
+ /* there is a pre-chunk part not blamed on the parent */
+ split[0].suspect = origin_incref(e->suspect);
+ split[0].lno = e->lno;
+ split[0].s_lno = e->s_lno;
+ split[0].num_lines = tlno - e->s_lno;
+ split[1].lno = e->lno + tlno - e->s_lno;
+ split[1].s_lno = plno;
+ } else {
+ split[1].lno = e->lno;
+ split[1].s_lno = plno + (e->s_lno - tlno);
+ }
+
+ if (same < e->s_lno + e->num_lines) {
+ /* there is a post-chunk part not blamed on parent */
+ split[2].suspect = origin_incref(e->suspect);
+ split[2].lno = e->lno + (same - e->s_lno);
+ split[2].s_lno = e->s_lno + (same - e->s_lno);
+ split[2].num_lines = e->s_lno + e->num_lines - same;
+ chunk_end_lno = split[2].lno;
+ } else {
+ chunk_end_lno = e->lno + e->num_lines;
+ }
+ split[1].num_lines = chunk_end_lno - split[1].lno;
+
+ /*
+ * if it turns out there is nothing to blame the parent for, forget about
+ * the splitting. !split[1].suspect signals this.
+ */
+ if (split[1].num_lines < 1)
+ return;
+ split[1].suspect = origin_incref(parent);
+}
+
+/*
+ * Link in a new blame entry to the scoreboard. Entries that cover the same
+ * line range have been removed from the scoreboard previously.
+ */
+static void add_blame_entry(git_blame *blame, git_blame__entry *e)
+{
+ git_blame__entry *ent, *prev = NULL;
+
+ origin_incref(e->suspect);
+
+ for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next)
+ prev = ent;
+
+ /* prev, if not NULL, is the last one that is below e */
+ e->prev = prev;
+ if (prev) {
+ e->next = prev->next;
+ prev->next = e;
+ } else {
+ e->next = blame->ent;
+ blame->ent = e;
+ }
+ if (e->next)
+ e->next->prev = e;
+}
+
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that is already on the linked list of the scoreboard.
+ * The origin of dst loses a refcnt while the origin of src gains one.
+ */
+static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
+{
+ git_blame__entry *p, *n;
+
+ p = dst->prev;
+ n = dst->next;
+ origin_incref(src->suspect);
+ origin_decref(dst->suspect);
+ memcpy(dst, src, sizeof(*src));
+ dst->prev = p;
+ dst->next = n;
+ dst->score = 0;
+}
+
+/*
+ * split_overlap() divided an existing blame e into up to three parts in split.
+ * Adjust the linked list of blames in the scoreboard to reflect the split.
+ */
+static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
+{
+ git_blame__entry *new_entry;
+
+ if (split[0].suspect && split[2].suspect) {
+ /* The first part (reuse storage for the existing entry e */
+ dup_entry(e, &split[0]);
+
+ /* The last part -- me */
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+
+ /* ... and the middle part -- parent */
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ } else if (!split[0].suspect && !split[2].suspect) {
+ /*
+ * The parent covers the entire area; reuse storage for e and replace it
+ * with the parent
+ */
+ dup_entry(e, &split[1]);
+ } else if (split[0].suspect) {
+ /* me and then parent */
+ dup_entry(e, &split[0]);
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ } else {
+ /* parent and then me */
+ dup_entry(e, &split[1]);
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ }
+}
+
+/*
+ * After splitting the blame, the origins used by the on-stack blame_entry
+ * should lose one refcnt each.
+ */
+static void decref_split(git_blame__entry *split)
+{
+ int i;
+ for (i=0; i<3; i++)
+ origin_decref(split[i].suspect);
+}
+
+/*
+ * Helper for blame_chunk(). blame_entry e is known to overlap with the patch
+ * hunk; split it and pass blame to the parent.
+ */
+static void blame_overlap(
+ git_blame *blame,
+ git_blame__entry *e,
+ size_t tlno,
+ size_t plno,
+ size_t same,
+ git_blame__origin *parent)
+{
+ git_blame__entry split[3] = {{0}};
+
+ split_overlap(split, e, tlno, plno, same, parent);
+ if (split[1].suspect)
+ split_blame(blame, split, e);
+ decref_split(split);
+}
+
+/*
+ * Process one hunk from the patch between the current suspect for blame_entry
+ * e and its parent. Find and split the overlap, and pass blame to the
+ * overlapping part to the parent.
+ */
+static void blame_chunk(
+ git_blame *blame,
+ size_t tlno,
+ size_t plno,
+ size_t same,
+ git_blame__origin *target,
+ git_blame__origin *parent)
+{
+ git_blame__entry *e;
+
+ for (e = blame->ent; e; e = e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (same <= e->s_lno)
+ continue;
+ if (tlno < e->s_lno + e->num_lines) {
+ blame_overlap(blame, e, tlno, plno, same, parent);
+ }
+ }
+}
+
+static int my_emit(
+ long start_a, long count_a,
+ long start_b, long count_b,
+ void *cb_data)
+{
+ blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data;
+
+ blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+
+ return 0;
+}
+
+static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
+{
+ const int blk = 1024;
+ long trimmed = 0, recovered = 0;
+ char *ap = a->ptr + a->size;
+ char *bp = b->ptr + b->size;
+ long smaller = (long)((a->size < b->size) ? a->size : b->size);
+
+ if (ctx)
+ return;
+
+ while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
+ trimmed += blk;
+ ap -= blk;
+ bp -= blk;
+ }
+
+ while (recovered < trimmed)
+ if (ap[recovered++] == '\n')
+ break;
+ a->size -= trimmed - recovered;
+ b->size -= trimmed - recovered;
+}
+
+static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {0};
+
+ xecfg.hunk_func = my_emit;
+ ecb.priv = cb_data;
+
+ trim_common_tail(&file_a, &file_b, 0);
+
+ if (file_a.size > GIT_XDIFF_MAX_SIZE ||
+ file_b.size > GIT_XDIFF_MAX_SIZE) {
+ giterr_set(GITERR_INVALID, "file too large to blame");
+ return -1;
+ }
+
+ return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
+}
+
+static void fill_origin_blob(git_blame__origin *o, mmfile_t *file)
+{
+ memset(file, 0, sizeof(*file));
+ if (o->blob) {
+ file->ptr = (char*)git_blob_rawcontent(o->blob);
+ file->size = (size_t)git_blob_rawsize(o->blob);
+ }
+}
+
+static int pass_blame_to_parent(
+ git_blame *blame,
+ git_blame__origin *target,
+ git_blame__origin *parent)
+{
+ size_t last_in_target;
+ mmfile_t file_p, file_o;
+ blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
+
+ if (!find_last_in_target(&last_in_target, blame, target))
+ return 1; /* nothing remains for this target */
+
+ fill_origin_blob(parent, &file_p);
+ fill_origin_blob(target, &file_o);
+
+ if (diff_hunks(file_p, file_o, &d) < 0)
+ return -1;
+
+ /* The reset (i.e. anything after tlno) are the same as the parent */
+ blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
+
+ return 0;
+}
+
+static int paths_on_dup(void **old, void *new)
+{
+ GIT_UNUSED(old);
+ git__free(new);
+ return -1;
+}
+
+static git_blame__origin* find_origin(
+ git_blame *blame,
+ git_commit *parent,
+ git_blame__origin *origin)
+{
+ git_blame__origin *porigin = NULL;
+ git_diff *difflist = NULL;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_tree *otree=NULL, *ptree=NULL;
+
+ /* Get the trees from this commit and its parent */
+ if (0 != git_commit_tree(&otree, origin->commit) ||
+ 0 != git_commit_tree(&ptree, parent))
+ goto cleanup;
+
+ /* Configure the diff */
+ diffopts.context_lines = 0;
+ diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
+
+ /* Check to see if files we're interested have changed */
+ diffopts.pathspec.count = blame->paths.length;
+ diffopts.pathspec.strings = (char**)blame->paths.contents;
+ if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
+ goto cleanup;
+
+ if (!git_diff_num_deltas(difflist)) {
+ /* No changes; copy data */
+ git_blame__get_origin(&porigin, blame, parent, origin->path);
+ } else {
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+ int i;
+
+ /* Generate a full diff between the two trees */
+ git_diff_free(difflist);
+ diffopts.pathspec.count = 0;
+ if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
+ goto cleanup;
+
+ /* Let diff find renames */
+ findopts.flags = GIT_DIFF_FIND_RENAMES;
+ if (0 != git_diff_find_similar(difflist, &findopts))
+ goto cleanup;
+
+ /* Find one that matches */
+ for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
+ const git_diff_delta *delta = git_diff_get_delta(difflist, i);
+
+ if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path))
+ {
+ git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path),
+ paths_on_dup);
+ make_origin(&porigin, parent, delta->old_file.path);
+ }
+ }
+ }
+
+cleanup:
+ git_diff_free(difflist);
+ git_tree_free(otree);
+ git_tree_free(ptree);
+ return porigin;
+}
+
+/*
+ * The blobs of origin and porigin exactly match, so everything origin is
+ * suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(git_blame *blame,
+ git_blame__origin *origin, git_blame__origin *porigin)
+{
+ git_blame__entry *e;
+
+ if (!porigin->blob)
+ git_object_lookup((git_object**)&porigin->blob, blame->repository,
+ git_blob_id(origin->blob), GIT_OBJ_BLOB);
+ for (e=blame->ent; e; e=e->next) {
+ if (!same_suspect(e->suspect, origin))
+ continue;
+ origin_incref(porigin);
+ origin_decref(e->suspect);
+ e->suspect = porigin;
+ }
+}
+
+static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
+{
+ git_commit *commit = origin->commit;
+ int i, num_parents;
+ git_blame__origin *sg_buf[16];
+ git_blame__origin *porigin, **sg_origin = sg_buf;
+ int ret, error = 0;
+
+ num_parents = git_commit_parentcount(commit);
+ if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
+ /* Stop at oldest specified commit */
+ num_parents = 0;
+ else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1)
+ /* Limit search to the first parent */
+ num_parents = 1;
+
+ if (!num_parents) {
+ git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
+ goto finish;
+ }
+ else if (num_parents < (int)ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
+
+ for (i=0; i<num_parents; i++) {
+ git_commit *p;
+ int j, same;
+
+ if (sg_origin[i])
+ continue;
+
+ if ((error = git_commit_parent(&p, origin->commit, i)) < 0)
+ goto finish;
+ porigin = find_origin(blame, p, origin);
+
+ if (!porigin) {
+ /*
+ * We only have to decrement the parent's
+ * reference count when no porigin has
+ * been created, as otherwise the commit
+ * is assigned to the created object.
+ */
+ git_commit_free(p);
+ continue;
+ }
+ if (porigin->blob && origin->blob &&
+ !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
+ pass_whole_blame(blame, origin, porigin);
+ origin_decref(porigin);
+ goto finish;
+ }
+ for (j = same = 0; j<i; j++)
+ if (sg_origin[j] &&
+ !git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
+ same = 1;
+ break;
+ }
+ if (!same)
+ sg_origin[i] = porigin;
+ else
+ origin_decref(porigin);
+ }
+
+ /* Standard blame */
+ for (i=0; i<num_parents; i++) {
+ git_blame__origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ if (!origin->previous) {
+ origin_incref(porigin);
+ origin->previous = porigin;
+ }
+
+ if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) {
+ if (ret < 0)
+ error = -1;
+
+ goto finish;
+ }
+ }
+
+ /* TODO: optionally find moves in parents' files */
+
+ /* TODO: optionally find copies in parents' files */
+
+finish:
+ for (i=0; i<num_parents; i++)
+ if (sg_origin[i])
+ origin_decref(sg_origin[i]);
+ if (sg_origin != sg_buf)
+ git__free(sg_origin);
+ return error;
+}
+
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
+static void coalesce(git_blame *blame)
+{
+ git_blame__entry *ent, *next;
+
+ for (ent=blame->ent; ent && (next = ent->next); ent = next) {
+ if (same_suspect(ent->suspect, next->suspect) &&
+ ent->guilty == next->guilty &&
+ ent->s_lno + ent->num_lines == next->s_lno)
+ {
+ ent->num_lines += next->num_lines;
+ ent->next = next->next;
+ if (ent->next)
+ ent->next->prev = ent;
+ origin_decref(next->suspect);
+ git__free(next);
+ ent->score = 0;
+ next = ent; /* again */
+ }
+ }
+}
+
+int git_blame__like_git(git_blame *blame, uint32_t opt)
+{
+ while (true) {
+ git_blame__entry *ent;
+ git_blame__origin *suspect = NULL;
+
+ /* Find a suspect to break down */
+ for (ent = blame->ent; !suspect && ent; ent = ent->next)
+ if (!ent->guilty)
+ suspect = ent->suspect;
+ if (!suspect)
+ return 0; /* all done */
+
+ /* We'll use this suspect later in the loop, so hold on to it for now. */
+ origin_incref(suspect);
+
+ if (pass_blame(blame, suspect, opt) < 0)
+ return -1;
+
+ /* Take responsibility for the remaining entries */
+ for (ent = blame->ent; ent; ent = ent->next) {
+ if (same_suspect(ent->suspect, suspect)) {
+ ent->guilty = true;
+ ent->is_boundary = !git_oid_cmp(
+ git_commit_id(suspect->commit),
+ &blame->options.oldest_commit);
+ }
+ }
+ origin_decref(suspect);
+ }
+
+ coalesce(blame);
+
+ return 0;
+}
+
+void git_blame__free_entry(git_blame__entry *ent)
+{
+ if (!ent) return;
+ origin_decref(ent->suspect);
+ git__free(ent);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_blame_git__
+#define INCLUDE_blame_git__
+
+#include "blame.h"
+
+int git_blame__get_origin(
+ git_blame__origin **out,
+ git_blame *sb,
+ git_commit *commit,
+ const char *path);
+void git_blame__free_entry(git_blame__entry *ent);
+int git_blame__like_git(git_blame *sb, uint32_t flags);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/common.h"
+#include "git2/object.h"
+#include "git2/repository.h"
+#include "git2/odb_backend.h"
+
+#include "common.h"
+#include "filebuf.h"
+#include "blob.h"
+#include "filter.h"
+#include "buf_text.h"
+
+const void *git_blob_rawcontent(const git_blob *blob)
+{
+ assert(blob);
+ return git_odb_object_data(blob->odb_object);
+}
+
+git_off_t git_blob_rawsize(const git_blob *blob)
+{
+ assert(blob);
+ return (git_off_t)git_odb_object_size(blob->odb_object);
+}
+
+int git_blob__getbuf(git_buf *buffer, git_blob *blob)
+{
+ return git_buf_set(
+ buffer,
+ git_odb_object_data(blob->odb_object),
+ git_odb_object_size(blob->odb_object));
+}
+
+void git_blob__free(void *blob)
+{
+ git_odb_object_free(((git_blob *)blob)->odb_object);
+ git__free(blob);
+}
+
+int git_blob__parse(void *blob, git_odb_object *odb_obj)
+{
+ assert(blob);
+ git_cached_obj_incref((git_cached_obj *)odb_obj);
+ ((git_blob *)blob)->odb_object = odb_obj;
+ return 0;
+}
+
+int git_blob_create_frombuffer(
+ git_oid *id, git_repository *repo, const void *buffer, size_t len)
+{
+ int error;
+ git_odb *odb;
+ git_odb_stream *stream;
+
+ assert(id && repo);
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
+ return error;
+
+ if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
+ error = git_odb_stream_finalize_write(id, stream);
+
+ git_odb_stream_free(stream);
+ return error;
+}
+
+static int write_file_stream(
+ git_oid *id, git_odb *odb, const char *path, git_off_t file_size)
+{
+ int fd, error;
+ char buffer[FILEIO_BUFSIZE];
+ git_odb_stream *stream = NULL;
+ ssize_t read_len = -1;
+ git_off_t written = 0;
+
+ if ((error = git_odb_open_wstream(
+ &stream, odb, file_size, GIT_OBJ_BLOB)) < 0)
+ return error;
+
+ if ((fd = git_futils_open_ro(path)) < 0) {
+ git_odb_stream_free(stream);
+ return -1;
+ }
+
+ while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ error = git_odb_stream_write(stream, buffer, read_len);
+ written += read_len;
+ }
+
+ p_close(fd);
+
+ if (written != file_size || read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read file into stream");
+ error = -1;
+ }
+
+ if (!error)
+ error = git_odb_stream_finalize_write(id, stream);
+
+ git_odb_stream_free(stream);
+ return error;
+}
+
+static int write_file_filtered(
+ git_oid *id,
+ git_off_t *size,
+ git_odb *odb,
+ const char *full_path,
+ git_filter_list *fl)
+{
+ int error;
+ git_buf tgt = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
+
+ /* Write the file to disk if it was properly filtered */
+ if (!error) {
+ *size = tgt.size;
+
+ error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB);
+ }
+
+ git_buf_free(&tgt);
+ return error;
+}
+
+static int write_symlink(
+ git_oid *id, git_odb *odb, const char *path, size_t link_size)
+{
+ char *link_data;
+ ssize_t read_len;
+ int error;
+
+ link_data = git__malloc(link_size);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(path, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ giterr_set(GITERR_OS, "Failed to create blob. Can't read symlink '%s'", path);
+ git__free(link_data);
+ return -1;
+ }
+
+ error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJ_BLOB);
+ git__free(link_data);
+ return error;
+}
+
+int git_blob__create_from_paths(
+ git_oid *id,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *content_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool try_load_filters)
+{
+ int error;
+ struct stat st;
+ git_odb *odb = NULL;
+ git_off_t size;
+ mode_t mode;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(hint_path || !try_load_filters);
+
+ if (!content_path) {
+ if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
+ return GIT_EBAREREPO;
+
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(repo), hint_path) < 0)
+ return -1;
+
+ content_path = path.ptr;
+ }
+
+ if ((error = git_path_lstat(content_path, &st)) < 0 ||
+ (error = git_repository_odb(&odb, repo)) < 0)
+ goto done;
+
+ if (S_ISDIR(st.st_mode)) {
+ giterr_set(GITERR_ODB, "cannot create blob from '%s'; it is a directory", content_path);
+ error = GIT_EDIRECTORY;
+ goto done;
+ }
+
+ if (out_st)
+ memcpy(out_st, &st, sizeof(st));
+
+ size = st.st_size;
+ mode = hint_mode ? hint_mode : st.st_mode;
+
+ if (S_ISLNK(mode)) {
+ error = write_symlink(id, odb, content_path, (size_t)size);
+ } else {
+ git_filter_list *fl = NULL;
+
+ if (try_load_filters)
+ /* Load the filters for writing this file to the ODB */
+ error = git_filter_list_load(
+ &fl, repo, NULL, hint_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT);
+
+ if (error < 0)
+ /* well, that didn't work */;
+ else if (fl == NULL)
+ /* No filters need to be applied to the document: we can stream
+ * directly from disk */
+ error = write_file_stream(id, odb, content_path, size);
+ else {
+ /* We need to apply one or more filters */
+ error = write_file_filtered(id, &size, odb, content_path, fl);
+
+ git_filter_list_free(fl);
+ }
+
+ /*
+ * TODO: eventually support streaming filtered files, for files
+ * which are bigger than a given threshold. This is not a priority
+ * because applying a filter in streaming mode changes the final
+ * size of the blob, and without knowing its final size, the blob
+ * cannot be written in stream mode to the ODB.
+ *
+ * The plan is to do streaming writes to a tempfile on disk and then
+ * opening streaming that file to the ODB, using
+ * `write_file_stream`.
+ *
+ * CAREFULLY DESIGNED APIS YO
+ */
+ }
+
+done:
+ git_odb_free(odb);
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_blob_create_fromworkdir(
+ git_oid *id, git_repository *repo, const char *path)
+{
+ return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
+}
+
+int git_blob_create_fromdisk(
+ git_oid *id, git_repository *repo, const char *path)
+{
+ int error;
+ git_buf full_path = GIT_BUF_INIT;
+ const char *workdir, *hintpath;
+
+ if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
+ git_buf_free(&full_path);
+ return error;
+ }
+
+ hintpath = git_buf_cstr(&full_path);
+ workdir = git_repository_workdir(repo);
+
+ if (workdir && !git__prefixcmp(hintpath, workdir))
+ hintpath += strlen(workdir);
+
+ error = git_blob__create_from_paths(
+ id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
+
+ git_buf_free(&full_path);
+ return error;
+}
+
+typedef struct {
+ git_writestream parent;
+ git_filebuf fbuf;
+ git_repository *repo;
+ char *hintpath;
+} blob_writestream;
+
+static int blob_writestream_close(git_writestream *_stream)
+{
+ blob_writestream *stream = (blob_writestream *) _stream;
+
+ git_filebuf_cleanup(&stream->fbuf);
+ return 0;
+}
+
+static void blob_writestream_free(git_writestream *_stream)
+{
+ blob_writestream *stream = (blob_writestream *) _stream;
+
+ git_filebuf_cleanup(&stream->fbuf);
+ git__free(stream->hintpath);
+ git__free(stream);
+}
+
+static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len)
+{
+ blob_writestream *stream = (blob_writestream *) _stream;
+
+ return git_filebuf_write(&stream->fbuf, buffer, len);
+}
+
+int git_blob_create_fromstream(git_writestream **out, git_repository *repo, const char *hintpath)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ blob_writestream *stream;
+
+ assert(out && repo);
+
+ stream = git__calloc(1, sizeof(blob_writestream));
+ GITERR_CHECK_ALLOC(stream);
+
+ if (hintpath) {
+ stream->hintpath = git__strdup(hintpath);
+ GITERR_CHECK_ALLOC(stream->hintpath);
+ }
+
+ stream->repo = repo;
+ stream->parent.write = blob_writestream_write;
+ stream->parent.close = blob_writestream_close;
+ stream->parent.free = blob_writestream_free;
+
+ if ((error = git_buf_joinpath(&path,
+ git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
+ 0666, 2 * 1024 * 1024)) < 0)
+ goto cleanup;
+
+ *out = (git_writestream *) stream;
+
+cleanup:
+ if (error < 0)
+ blob_writestream_free((git_writestream *) stream);
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_blob_create_fromstream_commit(git_oid *out, git_writestream *_stream)
+{
+ int error;
+ blob_writestream *stream = (blob_writestream *) _stream;
+
+ /*
+ * We can make this more officient by avoiding writing to
+ * disk, but for now let's re-use the helper functions we
+ * have.
+ */
+ if ((error = git_filebuf_flush(&stream->fbuf)) < 0)
+ goto cleanup;
+
+ error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock,
+ stream->hintpath, 0, !!stream->hintpath);
+
+cleanup:
+ blob_writestream_free(_stream);
+ return error;
+
+}
+
+int git_blob_is_binary(const git_blob *blob)
+{
+ git_buf content = GIT_BUF_INIT;
+
+ assert(blob);
+
+ git_buf_attach_notowned(&content, blob->odb_object->buffer,
+ min(blob->odb_object->cached.size,
+ GIT_FILTER_BYTES_TO_CHECK_NUL));
+ return git_buf_text_is_binary(&content);
+}
+
+int git_blob_filtered_content(
+ git_buf *out,
+ git_blob *blob,
+ const char *path,
+ int check_for_binary_data)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+
+ assert(blob && path && out);
+
+ git_buf_sanitize(out);
+
+ if (check_for_binary_data && git_blob_is_binary(blob))
+ return 0;
+
+ if (!(error = git_filter_list_load(
+ &fl, git_blob_owner(blob), blob, path,
+ GIT_FILTER_TO_WORKTREE, GIT_FILTER_DEFAULT))) {
+
+ error = git_filter_list_apply_to_blob(out, fl, blob);
+
+ git_filter_list_free(fl);
+ }
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_blob_h__
+#define INCLUDE_blob_h__
+
+#include "git2/blob.h"
+#include "repository.h"
+#include "odb.h"
+#include "fileops.h"
+
+struct git_blob {
+ git_object object;
+ git_odb_object *odb_object;
+};
+
+void git_blob__free(void *blob);
+int git_blob__parse(void *blob, git_odb_object *obj);
+int git_blob__getbuf(git_buf *buffer, git_blob *blob);
+
+extern int git_blob__create_from_paths(
+ git_oid *out_oid,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *full_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool apply_filters);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "config.h"
+#include "refspec.h"
+#include "refs.h"
+#include "remote.h"
+#include "annotated_commit.h"
+
+#include "git2/branch.h"
+
+static int retrieve_branch_reference(
+ git_reference **branch_reference_out,
+ git_repository *repo,
+ const char *branch_name,
+ int is_remote)
+{
+ git_reference *branch = NULL;
+ int error = 0;
+ char *prefix;
+ git_buf ref_name = GIT_BUF_INIT;
+
+ prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
+
+ if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0)
+ /* OOM */;
+ else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
+ giterr_set(
+ GITERR_REFERENCE, "Cannot locate %s branch '%s'",
+ is_remote ? "remote-tracking" : "local", branch_name);
+
+ *branch_reference_out = branch; /* will be NULL on error */
+
+ git_buf_free(&ref_name);
+ return error;
+}
+
+static int not_a_local_branch(const char *reference_name)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "Reference '%s' is not a local branch.", reference_name);
+ return -1;
+}
+
+static int create_branch(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ const char *from,
+ int force)
+{
+ int is_unmovable_head = 0;
+ git_reference *branch = NULL;
+ git_buf canonical_branch_name = GIT_BUF_INIT,
+ log_message = GIT_BUF_INIT;
+ int error = -1;
+ int bare = git_repository_is_bare(repository);
+
+ assert(branch_name && commit && ref_out);
+ assert(git_object_owner((const git_object *)commit) == repository);
+
+ if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
+ error = git_branch_is_head(branch);
+ git_reference_free(branch);
+ branch = NULL;
+
+ if (error < 0)
+ goto cleanup;
+
+ is_unmovable_head = error;
+ }
+
+ if (is_unmovable_head && force) {
+ giterr_set(GITERR_REFERENCE, "Cannot force update branch '%s' as it is "
+ "the current HEAD of the repository.", branch_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&log_message, "branch: Created from %s", from) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&branch, repository,
+ git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force,
+ git_buf_cstr(&log_message));
+
+ if (!error)
+ *ref_out = branch;
+
+cleanup:
+ git_buf_free(&canonical_branch_name);
+ git_buf_free(&log_message);
+ return error;
+}
+
+int git_branch_create(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ int force)
+{
+ return create_branch(ref_out, repository, branch_name, commit, git_oid_tostr_s(git_commit_id(commit)), force);
+}
+
+int git_branch_create_from_annotated(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_annotated_commit *commit,
+ int force)
+{
+ return create_branch(ref_out,
+ repository, branch_name, commit->commit, commit->description, force);
+}
+
+int git_branch_delete(git_reference *branch)
+{
+ int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(branch);
+
+ if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.",
+ git_reference_name(branch));
+ return GIT_ENOTFOUND;
+ }
+
+ if ((is_head = git_branch_is_head(branch)) < 0)
+ return is_head;
+
+ if (is_head) {
+ giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
+ "the current HEAD of the repository.", git_reference_name(branch));
+ return -1;
+ }
+
+ if (git_buf_join(&config_section, '.', "branch",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto on_error;
+
+ if (git_config_rename_section(
+ git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0)
+ goto on_error;
+
+ error = git_reference_delete(branch);
+
+on_error:
+ git_buf_free(&config_section);
+ return error;
+}
+
+typedef struct {
+ git_reference_iterator *iter;
+ unsigned int flags;
+} branch_iter;
+
+int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+ git_reference *ref;
+ int error;
+
+ while ((error = git_reference_next(&ref, iter->iter)) == 0) {
+ if ((iter->flags & GIT_BRANCH_LOCAL) &&
+ !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_LOCAL;
+
+ return 0;
+ } else if ((iter->flags & GIT_BRANCH_REMOTE) &&
+ !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_REMOTE;
+
+ return 0;
+ } else {
+ git_reference_free(ref);
+ }
+ }
+
+ return error;
+}
+
+int git_branch_iterator_new(
+ git_branch_iterator **out,
+ git_repository *repo,
+ git_branch_t list_flags)
+{
+ branch_iter *iter;
+
+ iter = git__calloc(1, sizeof(branch_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->flags = list_flags;
+
+ if (git_reference_iterator_new(&iter->iter, repo) < 0) {
+ git__free(iter);
+ return -1;
+ }
+
+ *out = (git_branch_iterator *) iter;
+
+ return 0;
+}
+
+void git_branch_iterator_free(git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+
+ if (iter == NULL)
+ return;
+
+ git_reference_iterator_free(iter->iter);
+ git__free(iter);
+}
+
+int git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force)
+{
+ git_buf new_reference_name = GIT_BUF_INIT,
+ old_config_section = GIT_BUF_INIT,
+ new_config_section = GIT_BUF_INIT,
+ log_message = GIT_BUF_INIT;
+ int error;
+
+ assert(branch && new_branch_name);
+
+ if (!git_reference_is_branch(branch))
+ return not_a_local_branch(git_reference_name(branch));
+
+ if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
+ goto done;
+
+ if ((error = git_buf_printf(&log_message, "branch: renamed %s to %s",
+ git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
+ goto done;
+
+ /* first update ref then config so failure won't trash config */
+
+ error = git_reference_rename(
+ out, branch, git_buf_cstr(&new_reference_name), force,
+ git_buf_cstr(&log_message));
+ if (error < 0)
+ goto done;
+
+ git_buf_join(&old_config_section, '.', "branch",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
+ git_buf_join(&new_config_section, '.', "branch", new_branch_name);
+
+ error = git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section));
+
+done:
+ git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
+ git_buf_free(&log_message);
+
+ return error;
+}
+
+int git_branch_lookup(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type)
+{
+ assert(ref_out && repo && branch_name);
+
+ return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
+}
+
+int git_branch_name(
+ const char **out,
+ const git_reference *ref)
+{
+ const char *branch_name;
+
+ assert(out && ref);
+
+ branch_name = ref->name;
+
+ if (git_reference_is_branch(ref)) {
+ branch_name += strlen(GIT_REFS_HEADS_DIR);
+ } else if (git_reference_is_remote(ref)) {
+ branch_name += strlen(GIT_REFS_REMOTES_DIR);
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Reference '%s' is neither a local nor a remote branch.", ref->name);
+ return -1;
+ }
+ *out = branch_name;
+ return 0;
+}
+
+static int retrieve_upstream_configuration(
+ git_buf *out,
+ const git_config *config,
+ const char *canonical_branch_name,
+ const char *format)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(&buf, format,
+ canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ return -1;
+
+ error = git_config_get_string_buf(out, config, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_branch_upstream_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ git_buf remote_name = GIT_BUF_INIT;
+ git_buf merge_name = GIT_BUF_INIT;
+ git_buf buf = GIT_BUF_INIT;
+ int error = -1;
+ git_remote *remote = NULL;
+ const git_refspec *refspec;
+ git_config *config;
+
+ assert(out && refname);
+
+ git_buf_sanitize(out);
+
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
+
+ if ((error = retrieve_upstream_configuration(
+ &remote_name, config, refname, "branch.%s.remote")) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_upstream_configuration(
+ &merge_name, config, refname, "branch.%s.merge")) < 0)
+ goto cleanup;
+
+ if (git_buf_len(&remote_name) == 0 || git_buf_len(&merge_name) == 0) {
+ giterr_set(GITERR_REFERENCE,
+ "branch '%s' does not have an upstream", refname);
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (strcmp(".", git_buf_cstr(&remote_name)) != 0) {
+ if ((error = git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name))) < 0)
+ goto cleanup;
+
+ refspec = git_remote__matching_refspec(remote, git_buf_cstr(&merge_name));
+ if (!refspec) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (git_refspec_transform(&buf, refspec, git_buf_cstr(&merge_name)) < 0)
+ goto cleanup;
+ } else
+ if (git_buf_set(&buf, git_buf_cstr(&merge_name), git_buf_len(&merge_name)) < 0)
+ goto cleanup;
+
+ error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
+
+cleanup:
+ git_config_free(config);
+ git_remote_free(remote);
+ git_buf_free(&remote_name);
+ git_buf_free(&merge_name);
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_branch_upstream_remote(git_buf *buf, git_repository *repo, const char *refname)
+{
+ int error;
+ git_config *cfg;
+
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ git_buf_sanitize(buf);
+
+ if ((error = retrieve_upstream_configuration(buf, cfg, refname, "branch.%s.remote")) < 0)
+ return error;
+
+ if (git_buf_len(buf) == 0) {
+ giterr_set(GITERR_REFERENCE, "branch '%s' does not have an upstream remote", refname);
+ error = GIT_ENOTFOUND;
+ git_buf_clear(buf);
+ }
+
+ return error;
+}
+
+int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
+{
+ git_strarray remote_list = {0};
+ size_t i;
+ git_remote *remote;
+ const git_refspec *fetchspec;
+ int error = 0;
+ char *remote_name = NULL;
+
+ assert(buf && repo && refname);
+
+ git_buf_sanitize(buf);
+
+ /* Verify that this is a remote branch */
+ if (!git_reference__is_remote(refname)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
+ refname);
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
+ goto cleanup;
+
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0)
+ continue;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, refname);
+ if (fetchspec) {
+ /* If we have not already set out yet, then set
+ * it to the matching remote name. Otherwise
+ * multiple remotes match this reference, and it
+ * is ambiguous. */
+ if (!remote_name) {
+ remote_name = remote_list.strings[i];
+ } else {
+ git_remote_free(remote);
+
+ giterr_set(GITERR_REFERENCE,
+ "Reference '%s' is ambiguous", refname);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ git_buf_clear(buf);
+ error = git_buf_puts(buf, remote_name);
+ } else {
+ giterr_set(GITERR_REFERENCE,
+ "Could not determine remote for '%s'", refname);
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ if (error < 0)
+ git_buf_free(buf);
+
+ git_strarray_free(&remote_list);
+ return error;
+}
+
+int git_branch_upstream(
+ git_reference **tracking_out,
+ const git_reference *branch)
+{
+ int error;
+ git_buf tracking_name = GIT_BUF_INIT;
+
+ if ((error = git_branch_upstream_name(&tracking_name,
+ git_reference_owner(branch), git_reference_name(branch))) < 0)
+ return error;
+
+ error = git_reference_lookup(
+ tracking_out,
+ git_reference_owner(branch),
+ git_buf_cstr(&tracking_name));
+
+ git_buf_free(&tracking_name);
+ return error;
+}
+
+static int unset_upstream(git_config *config, const char *shortname)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
+ return -1;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ return -1;
+}
+
+int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
+{
+ git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ git_reference *upstream;
+ git_repository *repo;
+ git_remote *remote = NULL;
+ git_config *config;
+ const char *name, *shortname;
+ int local, error;
+ const git_refspec *fetchspec;
+
+ name = git_reference_name(branch);
+ if (!git_reference__is_branch(name))
+ return not_a_local_branch(name);
+
+ if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
+ return -1;
+
+ shortname = name + strlen(GIT_REFS_HEADS_DIR);
+
+ if (upstream_name == NULL)
+ return unset_upstream(config, shortname);
+
+ repo = git_reference_owner(branch);
+
+ /* First we need to figure out whether it's a branch or remote-tracking */
+ if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
+ local = 1;
+ else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
+ local = 0;
+ else {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot set upstream for branch '%s'", shortname);
+ return GIT_ENOTFOUND;
+ }
+
+ /*
+ * If it's local, the remote is "." and the branch name is
+ * simply the refname. Otherwise we need to figure out what
+ * the remote-tracking branch's name on the remote is and use
+ * that.
+ */
+ if (local)
+ error = git_buf_puts(&value, ".");
+ else
+ error = git_branch_remote_name(&value, repo, git_reference_name(upstream));
+
+ if (error < 0)
+ goto on_error;
+
+ if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ if (local) {
+ git_buf_clear(&value);
+ if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
+ goto on_error;
+ } else {
+ /* Get the remoe-tracking branch's refname in its repo */
+ if (git_remote_lookup(&remote, repo, git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
+ git_buf_clear(&value);
+ if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0)
+ goto on_error;
+
+ git_remote_free(remote);
+ remote = NULL;
+ }
+
+ git_buf_clear(&key);
+ if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+
+ return 0;
+
+on_error:
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+ git_remote_free(remote);
+
+ return -1;
+}
+
+int git_branch_is_head(
+ const git_reference *branch)
+{
+ git_reference *head;
+ bool is_same = false;
+ int error;
+
+ assert(branch);
+
+ if (!git_reference_is_branch(branch))
+ return false;
+
+ error = git_repository_head(&head, git_reference_owner(branch));
+
+ if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
+ return false;
+
+ if (error < 0)
+ return -1;
+
+ is_same = strcmp(
+ git_reference_name(branch),
+ git_reference_name(head)) == 0;
+
+ git_reference_free(head);
+
+ return is_same;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_branch_h__
+#define INCLUDE_branch_h__
+
+#include "buffer.h"
+
+int git_branch_upstream__name(
+ git_buf *tracking_name,
+ git_repository *repo,
+ const char *canonical_branch_name);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "buf_text.h"
+
+int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with)
+{
+ const char *scan;
+ size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
+
+ if (!string)
+ return 0;
+
+ for (scan = string; *scan; ) {
+ /* count run of non-escaped characters */
+ count = strcspn(scan, esc_chars);
+ total += count;
+ scan += count;
+ /* count run of escaped characters */
+ count = strspn(scan, esc_chars);
+ total += count * (esc_len + 1);
+ scan += count;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, total, 1);
+ if (git_buf_grow_by(buf, alloclen) < 0)
+ return -1;
+
+ for (scan = string; *scan; ) {
+ count = strcspn(scan, esc_chars);
+
+ memmove(buf->ptr + buf->size, scan, count);
+ scan += count;
+ buf->size += count;
+
+ for (count = strspn(scan, esc_chars); count > 0; --count) {
+ /* copy escape sequence */
+ memmove(buf->ptr + buf->size, esc_with, esc_len);
+ buf->size += esc_len;
+ /* copy character to be escaped */
+ buf->ptr[buf->size] = *scan;
+ buf->size++;
+ scan++;
+ }
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_buf_text_unescape(git_buf *buf)
+{
+ buf->size = git__unescape(buf->ptr);
+}
+
+int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
+{
+ const char *scan = src->ptr;
+ const char *scan_end = src->ptr + src->size;
+ const char *next = memchr(scan, '\r', src->size);
+ size_t new_size;
+ char *out;
+
+ assert(tgt != src);
+
+ if (!next)
+ return git_buf_set(tgt, src->ptr, src->size);
+
+ /* reduce reallocs while in the loop */
+ GITERR_CHECK_ALLOC_ADD(&new_size, src->size, 1);
+ if (git_buf_grow(tgt, new_size) < 0)
+ return -1;
+
+ out = tgt->ptr;
+ tgt->size = 0;
+
+ /* Find the next \r and copy whole chunk up to there to tgt */
+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+ if (next > scan) {
+ size_t copylen = (size_t)(next - scan);
+ memcpy(out, scan, copylen);
+ out += copylen;
+ }
+
+ /* Do not drop \r unless it is followed by \n */
+ if (next + 1 == scan_end || next[1] != '\n')
+ *out++ = '\r';
+ }
+
+ /* Copy remaining input into dest */
+ if (scan < scan_end) {
+ size_t remaining = (size_t)(scan_end - scan);
+ memcpy(out, scan, remaining);
+ out += remaining;
+ }
+
+ tgt->size = (size_t)(out - tgt->ptr);
+ tgt->ptr[tgt->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
+{
+ const char *start = src->ptr;
+ const char *end = start + src->size;
+ const char *scan = start;
+ const char *next = memchr(scan, '\n', src->size);
+ size_t alloclen;
+
+ assert(tgt != src);
+
+ if (!next)
+ return git_buf_set(tgt, src->ptr, src->size);
+
+ /* attempt to reduce reallocs while in the loop */
+ GITERR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ if (git_buf_grow(tgt, alloclen) < 0)
+ return -1;
+ tgt->size = 0;
+
+ for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
+ size_t copylen = next - scan;
+
+ /* if we find mixed line endings, carry on */
+ if (copylen && next[-1] == '\r')
+ copylen--;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, copylen, 3);
+ if (git_buf_grow_by(tgt, alloclen) < 0)
+ return -1;
+
+ if (copylen) {
+ memcpy(tgt->ptr + tgt->size, scan, copylen);
+ tgt->size += copylen;
+ }
+
+ tgt->ptr[tgt->size++] = '\r';
+ tgt->ptr[tgt->size++] = '\n';
+ }
+
+ tgt->ptr[tgt->size] = '\0';
+ return git_buf_put(tgt, scan, end - scan);
+}
+
+int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
+{
+ size_t i;
+ const char *str, *pfx;
+
+ git_buf_clear(buf);
+
+ if (!strings || !strings->count)
+ return 0;
+
+ /* initialize common prefix to first string */
+ if (git_buf_sets(buf, strings->strings[0]) < 0)
+ return -1;
+
+ /* go through the rest of the strings, truncating to shared prefix */
+ for (i = 1; i < strings->count; ++i) {
+
+ for (str = strings->strings[i], pfx = buf->ptr;
+ *str && *str == *pfx; str++, pfx++)
+ /* scanning */;
+
+ git_buf_truncate(buf, pfx - buf->ptr);
+
+ if (!buf->size)
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_is_binary(const git_buf *buf)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ git_bom_t bom;
+ int printable = 0, nonprintable = 0;
+
+ scan += git_buf_text_detect_bom(&bom, buf, 0);
+
+ if (bom > GIT_BOM_UTF8)
+ return 1;
+
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ /* Printable characters are those above SPACE (0x1F) excluding DEL,
+ * and including BS, ESC and FF.
+ */
+ if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014')
+ printable++;
+ else if (c == '\0')
+ return true;
+ else if (!git__isspace(c))
+ nonprintable++;
+ }
+
+ return ((printable >> 7) < nonprintable);
+}
+
+bool git_buf_text_contains_nul(const git_buf *buf)
+{
+ return (memchr(buf->ptr, '\0', buf->size) != NULL);
+}
+
+int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf, size_t offset)
+{
+ const char *ptr;
+ size_t len;
+
+ *bom = GIT_BOM_NONE;
+ /* need at least 2 bytes after offset to look for any BOM */
+ if (buf->size < offset + 2)
+ return 0;
+
+ ptr = buf->ptr + offset;
+ len = buf->size - offset;
+
+ switch (*ptr++) {
+ case 0:
+ if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
+ *bom = GIT_BOM_UTF32_BE;
+ return 4;
+ }
+ break;
+ case '\xEF':
+ if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
+ *bom = GIT_BOM_UTF8;
+ return 3;
+ }
+ break;
+ case '\xFE':
+ if (*ptr == '\xFF') {
+ *bom = GIT_BOM_UTF16_BE;
+ return 2;
+ }
+ break;
+ case '\xFF':
+ if (*ptr != '\xFE')
+ break;
+ if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
+ *bom = GIT_BOM_UTF32_LE;
+ return 4;
+ } else {
+ *bom = GIT_BOM_UTF16_LE;
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ int skip;
+
+ memset(stats, 0, sizeof(*stats));
+
+ /* BOM detection */
+ skip = git_buf_text_detect_bom(&stats->bom, buf, 0);
+ if (skip_bom)
+ scan += skip;
+
+ /* Ignore EOF character */
+ if (buf->size > 0 && end[-1] == '\032')
+ end--;
+
+ /* Counting loop */
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if (c > 0x1F && c != 0x7F)
+ stats->printable++;
+ else switch (c) {
+ case '\0':
+ stats->nul++;
+ stats->nonprintable++;
+ break;
+ case '\n':
+ stats->lf++;
+ break;
+ case '\r':
+ stats->cr++;
+ if (scan < end && *scan == '\n')
+ stats->crlf++;
+ break;
+ case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
+ stats->printable++;
+ break;
+ default:
+ stats->nonprintable++;
+ break;
+ }
+ }
+
+ return (stats->nul > 0 ||
+ ((stats->printable >> 7) < stats->nonprintable));
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_buf_text_h__
+#define INCLUDE_buf_text_h__
+
+#include "buffer.h"
+
+typedef enum {
+ GIT_BOM_NONE = 0,
+ GIT_BOM_UTF8 = 1,
+ GIT_BOM_UTF16_LE = 2,
+ GIT_BOM_UTF16_BE = 3,
+ GIT_BOM_UTF32_LE = 4,
+ GIT_BOM_UTF32_BE = 5
+} git_bom_t;
+
+typedef struct {
+ git_bom_t bom; /* BOM found at head of text */
+ unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */
+ unsigned int printable, nonprintable; /* These are just approximations! */
+} git_buf_text_stats;
+
+/**
+ * Append string to buffer, prefixing each character from `esc_chars` with
+ * `esc_with` string.
+ *
+ * @param buf Buffer to append data to
+ * @param string String to escape and append
+ * @param esc_chars Characters to be escaped
+ * @param esc_with String to insert in from of each found character
+ * @return 0 on success, <0 on failure (probably allocation problem)
+ */
+extern int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with);
+
+/**
+ * Append string escaping characters that are regex special
+ */
+GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
+{
+ return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");
+}
+
+/**
+ * Unescape all characters in a buffer in place
+ *
+ * I.e. remove backslashes
+ */
+extern void git_buf_text_unescape(git_buf *buf);
+
+/**
+ * Replace all \r\n with \n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Replace all \n with \r\n. Does not modify existing \r\n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Fill buffer with the common prefix of a array of strings
+ *
+ * Buffer will be set to empty if there is no common prefix
+ */
+extern int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strs);
+
+/**
+ * Check quickly if buffer looks like it contains binary data
+ *
+ * @param buf Buffer to check
+ * @return true if buffer looks like non-text data
+ */
+extern bool git_buf_text_is_binary(const git_buf *buf);
+
+/**
+ * Check quickly if buffer contains a NUL byte
+ *
+ * @param buf Buffer to check
+ * @return true if buffer contains a NUL byte
+ */
+extern bool git_buf_text_contains_nul(const git_buf *buf);
+
+/**
+ * Check if a buffer begins with a UTF BOM
+ *
+ * @param bom Set to the type of BOM detected or GIT_BOM_NONE
+ * @param buf Buffer in which to check the first bytes for a BOM
+ * @param offset Offset into buffer to look for BOM
+ * @return Number of bytes of BOM data (or 0 if no BOM found)
+ */
+extern int git_buf_text_detect_bom(
+ git_bom_t *bom, const git_buf *buf, size_t offset);
+
+/**
+ * Gather stats for a piece of text
+ *
+ * Fill the `stats` structure with counts of unreadable characters, carriage
+ * returns, etc, so it can be used in heuristics. This automatically skips
+ * a trailing EOF (\032 character). Also it will look for a BOM at the
+ * start of the text and can be told to skip that as well.
+ *
+ * @param stats Structure to be filled in
+ * @param buf Text to process
+ * @param skip_bom Exclude leading BOM from stats if true
+ * @return Does the buffer heuristically look like binary data
+ */
+extern bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "buffer.h"
+#include "posix.h"
+#include "git2/buffer.h"
+#include "buf_text.h"
+#include <ctype.h>
+
+/* Used as default value for git_buf->ptr so that people can always
+ * assume ptr is non-NULL and zero terminated even for new git_bufs.
+ */
+char git_buf__initbuf[1];
+
+char git_buf__oom[1];
+
+#define ENSURE_SIZE(b, d) \
+ if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
+ return -1;
+
+
+void git_buf_init(git_buf *buf, size_t initial_size)
+{
+ buf->asize = 0;
+ buf->size = 0;
+ buf->ptr = git_buf__initbuf;
+
+ if (initial_size)
+ git_buf_grow(buf, initial_size);
+}
+
+int git_buf_try_grow(
+ git_buf *buf, size_t target_size, bool mark_oom)
+{
+ char *new_ptr;
+ size_t new_size;
+
+ if (buf->ptr == git_buf__oom)
+ return -1;
+
+ if (buf->asize == 0 && buf->size != 0) {
+ giterr_set(GITERR_INVALID, "cannot grow a borrowed buffer");
+ return GIT_EINVALID;
+ }
+
+ if (!target_size)
+ target_size = buf->size;
+
+ if (target_size <= buf->asize)
+ return 0;
+
+ if (buf->asize == 0) {
+ new_size = target_size;
+ new_ptr = NULL;
+ } else {
+ new_size = buf->asize;
+ new_ptr = buf->ptr;
+ }
+
+ /* grow the buffer size by 1.5, until it's big enough
+ * to fit our target size */
+ while (new_size < target_size)
+ new_size = (new_size << 1) - (new_size >> 1);
+
+ /* round allocation up to multiple of 8 */
+ new_size = (new_size + 7) & ~7;
+
+ if (new_size < buf->size) {
+ if (mark_oom)
+ buf->ptr = git_buf__oom;
+
+ giterr_set_oom();
+ return -1;
+ }
+
+ new_ptr = git__realloc(new_ptr, new_size);
+
+ if (!new_ptr) {
+ if (mark_oom) {
+ if (buf->ptr && (buf->ptr != git_buf__initbuf))
+ git__free(buf->ptr);
+ buf->ptr = git_buf__oom;
+ }
+ return -1;
+ }
+
+ buf->asize = new_size;
+ buf->ptr = new_ptr;
+
+ /* truncate the existing buffer size if necessary */
+ if (buf->size >= buf->asize)
+ buf->size = buf->asize - 1;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_grow(git_buf *buffer, size_t target_size)
+{
+ return git_buf_try_grow(buffer, target_size, true);
+}
+
+int git_buf_grow_by(git_buf *buffer, size_t additional_size)
+{
+ size_t newsize;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
+ buffer->ptr = git_buf__oom;
+ return -1;
+ }
+
+ return git_buf_try_grow(buffer, newsize, true);
+}
+
+void git_buf_free(git_buf *buf)
+{
+ if (!buf) return;
+
+ if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
+ git__free(buf->ptr);
+
+ git_buf_init(buf, 0);
+}
+
+void git_buf_sanitize(git_buf *buf)
+{
+ if (buf->ptr == NULL) {
+ assert(buf->size == 0 && buf->asize == 0);
+ buf->ptr = git_buf__initbuf;
+ } else if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+}
+
+void git_buf_clear(git_buf *buf)
+{
+ buf->size = 0;
+
+ if (!buf->ptr) {
+ buf->ptr = git_buf__initbuf;
+ buf->asize = 0;
+ }
+
+ if (buf->asize > 0)
+ buf->ptr[0] = '\0';
+}
+
+int git_buf_set(git_buf *buf, const void *data, size_t len)
+{
+ size_t alloclen;
+
+ if (len == 0 || data == NULL) {
+ git_buf_clear(buf);
+ } else {
+ if (data != buf->ptr) {
+ GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+ ENSURE_SIZE(buf, alloclen);
+ memmove(buf->ptr, data, len);
+ }
+
+ buf->size = len;
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+
+ }
+ return 0;
+}
+
+int git_buf_is_binary(const git_buf *buf)
+{
+ return git_buf_text_is_binary(buf);
+}
+
+int git_buf_contains_nul(const git_buf *buf)
+{
+ return git_buf_text_contains_nul(buf);
+}
+
+int git_buf_sets(git_buf *buf, const char *string)
+{
+ return git_buf_set(buf, string, string ? strlen(string) : 0);
+}
+
+int git_buf_putc(git_buf *buf, char c)
+{
+ size_t new_size;
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
+ ENSURE_SIZE(buf, new_size);
+ buf->ptr[buf->size++] = c;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_buf_putcn(git_buf *buf, char c, size_t len)
+{
+ size_t new_size;
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ memset(buf->ptr + buf->size, c, len);
+ buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_buf_put(git_buf *buf, const char *data, size_t len)
+{
+ if (len) {
+ size_t new_size;
+
+ assert(data);
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ memmove(buf->ptr + buf->size, data, len);
+ buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ }
+ return 0;
+}
+
+int git_buf_puts(git_buf *buf, const char *string)
+{
+ assert(string);
+ return git_buf_put(buf, string, strlen(string));
+}
+
+static const char base64_encode[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
+{
+ size_t extra = len % 3;
+ uint8_t *write, a, b, c;
+ const uint8_t *read = (const uint8_t *)data;
+ size_t blocks = (len / 3) + !!extra, alloclen;
+
+ GITERR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
+ GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
+
+ ENSURE_SIZE(buf, alloclen);
+ write = (uint8_t *)&buf->ptr[buf->size];
+
+ /* convert each run of 3 bytes into 4 output bytes */
+ for (len -= extra; len > 0; len -= 3) {
+ a = *read++;
+ b = *read++;
+ c = *read++;
+
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
+ *write++ = base64_encode[c & 0x3f];
+ }
+
+ if (extra > 0) {
+ a = *read++;
+ b = (extra > 1) ? *read++ : 0;
+
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
+ *write++ = '=';
+ }
+
+ buf->size = ((char *)write) - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+/* The inverse of base64_encode */
+static const int8_t base64_decode[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
+{
+ size_t i;
+ int8_t a, b, c, d;
+ size_t orig_size = buf->size, new_size;
+
+ if (len % 4) {
+ giterr_set(GITERR_INVALID, "invalid base64 input");
+ return -1;
+ }
+
+ assert(len % 4 == 0);
+ GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ for (i = 0; i < len; i += 4) {
+ if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
+ (b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
+ (c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
+ (d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ giterr_set(GITERR_INVALID, "invalid base64 input");
+ return -1;
+ }
+
+ buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
+ buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
+ }
+
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+static const char base85_encode[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
+
+int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
+{
+ size_t blocks = (len / 4) + !!(len % 4), alloclen;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+
+ ENSURE_SIZE(buf, alloclen);
+
+ while (len) {
+ uint32_t acc = 0;
+ char b85[5];
+ int i;
+
+ for (i = 24; i >= 0; i -= 8) {
+ uint8_t ch = *data++;
+ acc |= ch << i;
+
+ if (--len == 0)
+ break;
+ }
+
+ for (i = 4; i >= 0; i--) {
+ int val = acc % 85;
+ acc /= 85;
+
+ b85[i] = base85_encode[val];
+ }
+
+ for (i = 0; i < 5; i++)
+ buf->ptr[buf->size++] = b85[i];
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+/* The inverse of base85_encode */
+static const int8_t base85_decode[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77,
+ 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
+ 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+int git_buf_decode_base85(
+ git_buf *buf,
+ const char *base85,
+ size_t base85_len,
+ size_t output_len)
+{
+ size_t orig_size = buf->size, new_size;
+
+ if (base85_len % 5 ||
+ output_len > base85_len * 4 / 5) {
+ giterr_set(GITERR_INVALID, "invalid base85 input");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ while (output_len) {
+ unsigned acc = 0;
+ int de, cnt = 4;
+ unsigned char ch;
+ do {
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ acc = acc * 85 + de;
+ } while (--cnt);
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ /* Detect overflow. */
+ if (0xffffffff / 85 < acc ||
+ 0xffffffff - de < (acc *= 85))
+ goto on_error;
+
+ acc += de;
+
+ cnt = (output_len < 4) ? output_len : 4;
+ output_len -= cnt;
+ do {
+ acc = (acc << 8) | (acc >> 24);
+ buf->ptr[buf->size++] = acc;
+ } while (--cnt);
+ }
+
+ buf->ptr[buf->size] = 0;
+
+ return 0;
+
+on_error:
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ giterr_set(GITERR_INVALID, "invalid base85 input");
+ return -1;
+}
+
+int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
+{
+ size_t expected_size, new_size;
+ int len;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
+ GITERR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
+ ENSURE_SIZE(buf, expected_size);
+
+ while (1) {
+ va_list args;
+ va_copy(args, ap);
+
+ len = p_vsnprintf(
+ buf->ptr + buf->size,
+ buf->asize - buf->size,
+ format, args
+ );
+
+ va_end(args);
+
+ if (len < 0) {
+ git__free(buf->ptr);
+ buf->ptr = git_buf__oom;
+ return -1;
+ }
+
+ if ((size_t)len + 1 <= buf->asize - buf->size) {
+ buf->size += len;
+ break;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ }
+
+ return 0;
+}
+
+int git_buf_printf(git_buf *buf, const char *format, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = git_buf_vprintf(buf, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
+{
+ size_t copylen;
+
+ assert(data && datasize && buf);
+
+ data[0] = '\0';
+
+ if (buf->size == 0 || buf->asize <= 0)
+ return;
+
+ copylen = buf->size;
+ if (copylen > datasize - 1)
+ copylen = datasize - 1;
+ memmove(data, buf->ptr, copylen);
+ data[copylen] = '\0';
+}
+
+void git_buf_consume(git_buf *buf, const char *end)
+{
+ if (end > buf->ptr && end <= buf->ptr + buf->size) {
+ size_t consumed = end - buf->ptr;
+ memmove(buf->ptr, end, buf->size - consumed);
+ buf->size -= consumed;
+ buf->ptr[buf->size] = '\0';
+ }
+}
+
+void git_buf_truncate(git_buf *buf, size_t len)
+{
+ if (len >= buf->size)
+ return;
+
+ buf->size = len;
+ if (buf->size < buf->asize)
+ buf->ptr[buf->size] = '\0';
+}
+
+void git_buf_shorten(git_buf *buf, size_t amount)
+{
+ if (buf->size > amount)
+ git_buf_truncate(buf, buf->size - amount);
+ else
+ git_buf_clear(buf);
+}
+
+void git_buf_rtruncate_at_char(git_buf *buf, char separator)
+{
+ ssize_t idx = git_buf_rfind_next(buf, separator);
+ git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
+}
+
+void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
+{
+ git_buf t = *buf_a;
+ *buf_a = *buf_b;
+ *buf_b = t;
+}
+
+char *git_buf_detach(git_buf *buf)
+{
+ char *data = buf->ptr;
+
+ if (buf->asize == 0 || buf->ptr == git_buf__oom)
+ return NULL;
+
+ git_buf_init(buf, 0);
+
+ return data;
+}
+
+void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
+{
+ git_buf_free(buf);
+
+ if (ptr) {
+ buf->ptr = ptr;
+ buf->size = strlen(ptr);
+ if (asize)
+ buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
+ else /* pass 0 to fall back on strlen + 1 */
+ buf->asize = buf->size + 1;
+ } else {
+ git_buf_grow(buf, asize);
+ }
+}
+
+void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size)
+{
+ if (git_buf_is_allocated(buf))
+ git_buf_free(buf);
+
+ if (!size) {
+ git_buf_init(buf, 0);
+ } else {
+ buf->ptr = (char *)ptr;
+ buf->asize = 0;
+ buf->size = size;
+ }
+}
+
+int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
+{
+ va_list ap;
+ int i;
+ size_t total_size = 0, original_size = buf->size;
+ char *out, *original = buf->ptr;
+
+ if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
+ ++total_size; /* space for initial separator */
+
+ /* Make two passes to avoid multiple reallocation */
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char* segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ segment_len = strlen(segment);
+
+ GITERR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
+
+ if (segment_len == 0 || segment[segment_len - 1] != separator)
+ GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
+ }
+ va_end(ap);
+
+ /* expand buffer if needed */
+ if (total_size == 0)
+ return 0;
+
+ GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
+ if (git_buf_grow_by(buf, total_size) < 0)
+ return -1;
+
+ out = buf->ptr + buf->size;
+
+ /* append separator to existing buf if needed */
+ if (buf->size > 0 && out[-1] != separator)
+ *out++ = separator;
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char* segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ /* deal with join that references buffer's original content */
+ if (segment >= original && segment < original + original_size) {
+ size_t offset = (segment - original);
+ segment = buf->ptr + offset;
+ segment_len = original_size - offset;
+ } else {
+ segment_len = strlen(segment);
+ }
+
+ /* skip leading separators */
+ if (out > buf->ptr && out[-1] == separator)
+ while (segment_len > 0 && *segment == separator) {
+ segment++;
+ segment_len--;
+ }
+
+ /* copy over next buffer */
+ if (segment_len > 0) {
+ memmove(out, segment, segment_len);
+ out += segment_len;
+ }
+
+ /* append trailing separator (except for last item) */
+ if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
+ *out++ = separator;
+ }
+ va_end(ap);
+
+ /* set size based on num characters actually written */
+ buf->size = out - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_join(
+ git_buf *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b)
+{
+ size_t strlen_a = str_a ? strlen(str_a) : 0;
+ size_t strlen_b = strlen(str_b);
+ size_t alloc_len;
+ int need_sep = 0;
+ ssize_t offset_a = -1;
+
+ /* not safe to have str_b point internally to the buffer */
+ assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+
+ /* figure out if we need to insert a separator */
+ if (separator && strlen_a) {
+ while (*str_b == separator) { str_b++; strlen_b--; }
+ if (str_a[strlen_a - 1] != separator)
+ need_sep = 1;
+ }
+
+ /* str_a could be part of the buffer */
+ if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
+ offset_a = str_a - buf->ptr;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+ if (git_buf_grow(buf, alloc_len) < 0)
+ return -1;
+ assert(buf->ptr);
+
+ /* fix up internal pointers */
+ if (offset_a >= 0)
+ str_a = buf->ptr + offset_a;
+
+ /* do the actual copying */
+ if (offset_a != 0 && str_a)
+ memmove(buf->ptr, str_a, strlen_a);
+ if (need_sep)
+ buf->ptr[strlen_a] = separator;
+ memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
+
+ buf->size = strlen_a + strlen_b + need_sep;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_join3(
+ git_buf *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b,
+ const char *str_c)
+{
+ size_t len_a = strlen(str_a),
+ len_b = strlen(str_b),
+ len_c = strlen(str_c),
+ len_total;
+ int sep_a = 0, sep_b = 0;
+ char *tgt;
+
+ /* for this function, disallow pointers into the existing buffer */
+ assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
+ assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+ assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
+
+ if (separator) {
+ if (len_a > 0) {
+ while (*str_b == separator) { str_b++; len_b--; }
+ sep_a = (str_a[len_a - 1] != separator);
+ }
+ if (len_a > 0 || len_b > 0)
+ while (*str_c == separator) { str_c++; len_c--; }
+ if (len_b > 0)
+ sep_b = (str_b[len_b - 1] != separator);
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
+ GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
+ GITERR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
+ GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
+ GITERR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
+ if (git_buf_grow(buf, len_total) < 0)
+ return -1;
+
+ tgt = buf->ptr;
+
+ if (len_a) {
+ memcpy(tgt, str_a, len_a);
+ tgt += len_a;
+ }
+ if (sep_a)
+ *tgt++ = separator;
+ if (len_b) {
+ memcpy(tgt, str_b, len_b);
+ tgt += len_b;
+ }
+ if (sep_b)
+ *tgt++ = separator;
+ if (len_c)
+ memcpy(tgt, str_c, len_c);
+
+ buf->size = len_a + sep_a + len_b + sep_b + len_c;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_buf_rtrim(git_buf *buf)
+{
+ while (buf->size > 0) {
+ if (!git__isspace(buf->ptr[buf->size - 1]))
+ break;
+
+ buf->size--;
+ }
+
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+}
+
+int git_buf_cmp(const git_buf *a, const git_buf *b)
+{
+ int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
+ return (result != 0) ? result :
+ (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
+}
+
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert)
+{
+ char *splice_loc;
+ size_t new_size, alloc_size;
+
+ assert(buf && where <= buf->size && nb_to_remove <= buf->size - where);
+
+ splice_loc = buf->ptr + where;
+
+ /* Ported from git.git
+ * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
+ */
+ GITERR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
+ GITERR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
+ ENSURE_SIZE(buf, alloc_size);
+
+ memmove(splice_loc + nb_to_insert,
+ splice_loc + nb_to_remove,
+ buf->size - where - nb_to_remove);
+
+ memcpy(splice_loc, data, nb_to_insert);
+
+ buf->size = new_size;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_buf_quote(git_buf *buf)
+{
+ const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
+ git_buf quoted = GIT_BUF_INIT;
+ size_t i = 0;
+ bool quote = false;
+ int error = 0;
+
+ /* walk to the first char that needs quoting */
+ if (buf->size && buf->ptr[0] == '!')
+ quote = true;
+
+ for (i = 0; !quote && i < buf->size; i++) {
+ if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
+ buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
+ quote = true;
+ break;
+ }
+ }
+
+ if (!quote)
+ goto done;
+
+ git_buf_putc("ed, '"');
+ git_buf_put("ed, buf->ptr, i);
+
+ for (; i < buf->size; i++) {
+ /* whitespace - use the map above, which is ordered by ascii value */
+ if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
+ git_buf_putc("ed, '\\');
+ git_buf_putc("ed, whitespace[buf->ptr[i] - '\a']);
+ }
+
+ /* double quote and backslash must be escaped */
+ else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
+ git_buf_putc("ed, '\\');
+ git_buf_putc("ed, buf->ptr[i]);
+ }
+
+ /* escape anything unprintable as octal */
+ else if (buf->ptr[i] != ' ' &&
+ (buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
+ git_buf_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]);
+ }
+
+ /* yay, printable! */
+ else {
+ git_buf_putc("ed, buf->ptr[i]);
+ }
+ }
+
+ git_buf_putc("ed, '"');
+
+ if (git_buf_oom("ed)) {
+ error = -1;
+ goto done;
+ }
+
+ git_buf_swap("ed, buf);
+
+done:
+ git_buf_free("ed);
+ return error;
+}
+
+/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_buf_unquote(git_buf *buf)
+{
+ size_t i, j;
+ char ch;
+
+ git_buf_rtrim(buf);
+
+ if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
+ goto invalid;
+
+ for (i = 0, j = 1; j < buf->size-1; i++, j++) {
+ ch = buf->ptr[j];
+
+ if (ch == '\\') {
+ if (j == buf->size-2)
+ goto invalid;
+
+ ch = buf->ptr[++j];
+
+ switch (ch) {
+ /* \" or \\ simply copy the char in */
+ case '"': case '\\':
+ break;
+
+ /* add the appropriate escaped char */
+ case 'a': ch = '\a'; break;
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
+ case 'n': ch = '\n'; break;
+ case 'r': ch = '\r'; break;
+ case 't': ch = '\t'; break;
+ case 'v': ch = '\v'; break;
+
+ /* \xyz digits convert to the char*/
+ case '0': case '1': case '2': case '3':
+ if (j == buf->size-3) {
+ giterr_set(GITERR_INVALID,
+ "Truncated quoted character \\%c", ch);
+ return -1;
+ }
+
+ if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
+ buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
+ giterr_set(GITERR_INVALID,
+ "Truncated quoted character \\%c%c%c",
+ buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
+ return -1;
+ }
+
+ ch = ((buf->ptr[j] - '0') << 6) |
+ ((buf->ptr[j+1] - '0') << 3) |
+ (buf->ptr[j+2] - '0');
+ j += 2;
+ break;
+
+ default:
+ giterr_set(GITERR_INVALID, "Invalid quoted character \\%c", ch);
+ return -1;
+ }
+ }
+
+ buf->ptr[i] = ch;
+ }
+
+ buf->ptr[i] = '\0';
+ buf->size = i;
+
+ return 0;
+
+invalid:
+ giterr_set(GITERR_INVALID, "Invalid quoted line");
+ return -1;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_buffer_h__
+#define INCLUDE_buffer_h__
+
+#include "common.h"
+#include "git2/strarray.h"
+#include "git2/buffer.h"
+
+/* typedef struct {
+ * char *ptr;
+ * size_t asize, size;
+ * } git_buf;
+ */
+
+extern char git_buf__initbuf[];
+extern char git_buf__oom[];
+
+/* Use to initialize buffer structure when git_buf is on stack */
+#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 }
+
+GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf)
+{
+ return (buf->ptr != NULL && buf->asize > 0);
+}
+
+/**
+ * Initialize a git_buf structure.
+ *
+ * For the cases where GIT_BUF_INIT cannot be used to do static
+ * initialization.
+ */
+extern void git_buf_init(git_buf *buf, size_t initial_size);
+
+/**
+ * Resize the buffer allocation to make more space.
+ *
+ * This will attempt to grow the buffer to accommodate the additional size.
+ * It is similar to `git_buf_grow`, but performs the new size calculation,
+ * checking for overflow.
+ *
+ * Like `git_buf_grow`, if this is a user-supplied buffer, this will allocate
+ * a new buffer.
+ */
+extern int git_buf_grow_by(git_buf *buffer, size_t additional_size);
+
+/**
+ * Attempt to grow the buffer to hold at least `target_size` bytes.
+ *
+ * If the allocation fails, this will return an error. If `mark_oom` is true,
+ * this will mark the buffer as invalid for future operations; if false,
+ * existing buffer content will be preserved, but calling code must handle
+ * that buffer was not expanded. If `preserve_external` is true, then any
+ * existing data pointed to be `ptr` even if `asize` is zero will be copied
+ * into the newly allocated buffer.
+ */
+extern int git_buf_try_grow(
+ git_buf *buf, size_t target_size, bool mark_oom);
+
+/**
+ * Sanitizes git_buf structures provided from user input. Users of the
+ * library, when providing git_buf's, may wish to provide a NULL ptr for
+ * ease of handling. The buffer routines, however, expect a non-NULL ptr
+ * always. This helper method simply handles NULL input, converting to a
+ * git_buf__initbuf.
+ */
+extern void git_buf_sanitize(git_buf *buf);
+
+extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
+extern char *git_buf_detach(git_buf *buf);
+extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize);
+
+/* Populates a `git_buf` where the contents are not "owned" by the
+ * buffer, and calls to `git_buf_free` will not free the given buf.
+ */
+extern void git_buf_attach_notowned(
+ git_buf *buf, const char *ptr, size_t size);
+
+/**
+ * Test if there have been any reallocation failures with this git_buf.
+ *
+ * Any function that writes to a git_buf can fail due to memory allocation
+ * issues. If one fails, the git_buf will be marked with an OOM error and
+ * further calls to modify the buffer will fail. Check git_buf_oom() at the
+ * end of your sequence and it will be true if you ran out of memory at any
+ * point with that buffer.
+ *
+ * @return false if no error, true if allocation error
+ */
+GIT_INLINE(bool) git_buf_oom(const git_buf *buf)
+{
+ return (buf->ptr == git_buf__oom);
+}
+
+/*
+ * Functions below that return int value error codes will return 0 on
+ * success or -1 on failure (which generally means an allocation failed).
+ * Using a git_buf where the allocation has failed with result in -1 from
+ * all further calls using that buffer. As a result, you can ignore the
+ * return code of these functions and call them in a series then just call
+ * git_buf_oom at the end.
+ */
+int git_buf_sets(git_buf *buf, const char *string);
+int git_buf_putc(git_buf *buf, char c);
+int git_buf_putcn(git_buf *buf, char c, size_t len);
+int git_buf_put(git_buf *buf, const char *data, size_t len);
+int git_buf_puts(git_buf *buf, const char *string);
+int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
+int git_buf_vprintf(git_buf *buf, const char *format, va_list ap);
+void git_buf_clear(git_buf *buf);
+void git_buf_consume(git_buf *buf, const char *end);
+void git_buf_truncate(git_buf *buf, size_t len);
+void git_buf_shorten(git_buf *buf, size_t amount);
+void git_buf_rtruncate_at_char(git_buf *path, char separator);
+
+/** General join with separator */
+int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
+/** Fast join of two strings - first may legally point into `buf` data */
+int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
+/** Fast join of three strings - cannot reference `buf` data */
+int git_buf_join3(git_buf *buf, char separator, const char *str_a, const char *str_b, const char *str_c);
+
+/**
+ * Join two strings as paths, inserting a slash between as needed.
+ * @return 0 on success, -1 on failure
+ */
+GIT_INLINE(int) git_buf_joinpath(git_buf *buf, const char *a, const char *b)
+{
+ return git_buf_join(buf, '/', a, b);
+}
+
+GIT_INLINE(const char *) git_buf_cstr(const git_buf *buf)
+{
+ return buf->ptr;
+}
+
+GIT_INLINE(size_t) git_buf_len(const git_buf *buf)
+{
+ return buf->size;
+}
+
+void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf);
+
+#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1)
+
+GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch)
+{
+ ssize_t idx = (ssize_t)buf->size - 1;
+ while (idx >= 0 && buf->ptr[idx] == ch) idx--;
+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)
+{
+ ssize_t idx = (ssize_t)buf->size - 1;
+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)
+{
+ void *found = memchr(buf->ptr, ch, buf->size);
+ return found ? (ssize_t)((const char *)found - buf->ptr) : -1;
+}
+
+/* Remove whitespace from the end of the buffer */
+void git_buf_rtrim(git_buf *buf);
+
+int git_buf_cmp(const git_buf *a, const git_buf *b);
+
+/* Quote and unquote a buffer as specified in
+ * http://marc.info/?l=git&m=112927316408690&w=2
+ */
+int git_buf_quote(git_buf *buf);
+int git_buf_unquote(git_buf *buf);
+
+/* Write data as base64 encoded in buffer */
+int git_buf_encode_base64(git_buf *buf, const char *data, size_t len);
+/* Decode the given bas64 and write the result to the buffer */
+int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len);
+
+/* Write data as "base85" encoded in buffer */
+int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
+/* Decode the given "base85" and write the result to the buffer */
+int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len);
+
+/*
+ * Insert, remove or replace a portion of the buffer.
+ *
+ * @param buf The buffer to work with
+ *
+ * @param where The location in the buffer where the transformation
+ * should be applied.
+ *
+ * @param nb_to_remove The number of chars to be removed. 0 to not
+ * remove any character in the buffer.
+ *
+ * @param data A pointer to the data which should be inserted.
+ *
+ * @param nb_to_insert The number of chars to be inserted. 0 to not
+ * insert any character from the buffer.
+ *
+ * @return 0 or an error code.
+ */
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "commit.h"
+#include "thread-utils.h"
+#include "util.h"
+#include "cache.h"
+#include "odb.h"
+#include "object.h"
+#include "git2/oid.h"
+
+GIT__USE_OIDMAP
+
+bool git_cache__enabled = true;
+ssize_t git_cache__max_storage = (256 * 1024 * 1024);
+git_atomic_ssize git_cache__current_storage = {0};
+
+static size_t git_cache__max_object_size[8] = {
+ 0, /* GIT_OBJ__EXT1 */
+ 4096, /* GIT_OBJ_COMMIT */
+ 4096, /* GIT_OBJ_TREE */
+ 0, /* GIT_OBJ_BLOB */
+ 4096, /* GIT_OBJ_TAG */
+ 0, /* GIT_OBJ__EXT2 */
+ 0, /* GIT_OBJ_OFS_DELTA */
+ 0 /* GIT_OBJ_REF_DELTA */
+};
+
+int git_cache_set_max_object_size(git_otype type, size_t size)
+{
+ if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
+ giterr_set(GITERR_INVALID, "type out of range");
+ return -1;
+ }
+
+ git_cache__max_object_size[type] = size;
+ return 0;
+}
+
+void git_cache_dump_stats(git_cache *cache)
+{
+ git_cached_obj *object;
+
+ if (kh_size(cache->map) == 0)
+ return;
+
+ printf("Cache %p: %d items cached, %"PRIdZ" bytes\n",
+ cache, kh_size(cache->map), cache->used_memory);
+
+ kh_foreach_value(cache->map, object, {
+ char oid_str[9];
+ printf(" %s%c %s (%"PRIuZ")\n",
+ git_object_type2string(object->type),
+ object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
+ git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
+ object->size
+ );
+ });
+}
+
+int git_cache_init(git_cache *cache)
+{
+ memset(cache, 0, sizeof(*cache));
+ cache->map = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(cache->map);
+ if (git_rwlock_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize cache rwlock");
+ return -1;
+ }
+ return 0;
+}
+
+/* called with lock */
+static void clear_cache(git_cache *cache)
+{
+ git_cached_obj *evict = NULL;
+
+ if (kh_size(cache->map) == 0)
+ return;
+
+ kh_foreach_value(cache->map, evict, {
+ git_cached_obj_decref(evict);
+ });
+
+ kh_clear(oid, cache->map);
+ git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
+ cache->used_memory = 0;
+}
+
+void git_cache_clear(git_cache *cache)
+{
+ if (git_rwlock_wrlock(&cache->lock) < 0)
+ return;
+
+ clear_cache(cache);
+
+ git_rwlock_wrunlock(&cache->lock);
+}
+
+void git_cache_free(git_cache *cache)
+{
+ git_cache_clear(cache);
+ git_oidmap_free(cache->map);
+ git_rwlock_free(&cache->lock);
+ git__memzero(cache, sizeof(*cache));
+}
+
+/* Called with lock */
+static void cache_evict_entries(git_cache *cache)
+{
+ uint32_t seed = rand();
+ size_t evict_count = 8;
+ ssize_t evicted_memory = 0;
+
+ /* do not infinite loop if there's not enough entries to evict */
+ if (evict_count > kh_size(cache->map)) {
+ clear_cache(cache);
+ return;
+ }
+
+ while (evict_count > 0) {
+ khiter_t pos = seed++ % kh_end(cache->map);
+
+ if (kh_exist(cache->map, pos)) {
+ git_cached_obj *evict = kh_val(cache->map, pos);
+
+ evict_count--;
+ evicted_memory += evict->size;
+ git_cached_obj_decref(evict);
+
+ kh_del(oid, cache->map, pos);
+ }
+ }
+
+ cache->used_memory -= evicted_memory;
+ git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
+}
+
+static bool cache_should_store(git_otype object_type, size_t object_size)
+{
+ size_t max_size = git_cache__max_object_size[object_type];
+ return git_cache__enabled && object_size < max_size;
+}
+
+static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
+{
+ khiter_t pos;
+ git_cached_obj *entry = NULL;
+
+ if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
+ return NULL;
+
+ pos = kh_get(oid, cache->map, oid);
+ if (pos != kh_end(cache->map)) {
+ entry = kh_val(cache->map, pos);
+
+ if (flags && entry->flags != flags) {
+ entry = NULL;
+ } else {
+ git_cached_obj_incref(entry);
+ }
+ }
+
+ git_rwlock_rdunlock(&cache->lock);
+
+ return entry;
+}
+
+static void *cache_store(git_cache *cache, git_cached_obj *entry)
+{
+ khiter_t pos;
+
+ git_cached_obj_incref(entry);
+
+ if (!git_cache__enabled && cache->used_memory > 0) {
+ git_cache_clear(cache);
+ return entry;
+ }
+
+ if (!cache_should_store(entry->type, entry->size))
+ return entry;
+
+ if (git_rwlock_wrlock(&cache->lock) < 0)
+ return entry;
+
+ /* soften the load on the cache */
+ if (git_cache__current_storage.val > git_cache__max_storage)
+ cache_evict_entries(cache);
+
+ pos = kh_get(oid, cache->map, &entry->oid);
+
+ /* not found */
+ if (pos == kh_end(cache->map)) {
+ int rval;
+
+ pos = kh_put(oid, cache->map, &entry->oid, &rval);
+ if (rval >= 0) {
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ git_cached_obj_incref(entry);
+ cache->used_memory += entry->size;
+ git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
+ }
+ }
+ /* found */
+ else {
+ git_cached_obj *stored_entry = kh_val(cache->map, pos);
+
+ if (stored_entry->flags == entry->flags) {
+ git_cached_obj_decref(entry);
+ git_cached_obj_incref(stored_entry);
+ entry = stored_entry;
+ } else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
+ entry->flags == GIT_CACHE_STORE_PARSED) {
+ git_cached_obj_decref(stored_entry);
+ git_cached_obj_incref(entry);
+
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ } else {
+ /* NO OP */
+ }
+ }
+
+ git_rwlock_wrunlock(&cache->lock);
+ return entry;
+}
+
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_RAW;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+void *git_cache_store_parsed(git_cache *cache, git_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_PARSED;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
+}
+
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
+}
+
+void *git_cache_get_any(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
+}
+
+void git_cached_obj_decref(void *_obj)
+{
+ git_cached_obj *obj = _obj;
+
+ if (git_atomic_dec(&obj->refcount) == 0) {
+ switch (obj->flags) {
+ case GIT_CACHE_STORE_RAW:
+ git_odb_object__free(_obj);
+ break;
+
+ case GIT_CACHE_STORE_PARSED:
+ git_object__free(_obj);
+ break;
+
+ default:
+ git__free(_obj);
+ break;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_cache_h__
+#define INCLUDE_cache_h__
+
+#include "git2/common.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+
+#include "thread-utils.h"
+#include "oidmap.h"
+
+enum {
+ GIT_CACHE_STORE_ANY = 0,
+ GIT_CACHE_STORE_RAW = 1,
+ GIT_CACHE_STORE_PARSED = 2
+};
+
+typedef struct {
+ git_oid oid;
+ int16_t type; /* git_otype value */
+ uint16_t flags; /* GIT_CACHE_STORE value */
+ size_t size;
+ git_atomic refcount;
+} git_cached_obj;
+
+typedef struct {
+ git_oidmap *map;
+ git_rwlock lock;
+ ssize_t used_memory;
+} git_cache;
+
+extern bool git_cache__enabled;
+extern ssize_t git_cache__max_storage;
+extern git_atomic_ssize git_cache__current_storage;
+
+int git_cache_set_max_object_size(git_otype type, size_t size);
+
+int git_cache_init(git_cache *cache);
+void git_cache_free(git_cache *cache);
+void git_cache_clear(git_cache *cache);
+
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry);
+void *git_cache_store_parsed(git_cache *cache, git_object *entry);
+
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid);
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid);
+void *git_cache_get_any(git_cache *cache, const git_oid *oid);
+
+GIT_INLINE(size_t) git_cache_size(git_cache *cache)
+{
+ return (size_t)kh_size(cache->map);
+}
+
+GIT_INLINE(void) git_cached_obj_incref(void *_obj)
+{
+ git_cached_obj *obj = _obj;
+ git_atomic_inc(&obj->refcount);
+}
+
+void git_cached_obj_decref(void *_obj);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_compat_h__
+#define INCLUDE_compat_h__
+
+#include <stdarg.h>
+
+/*
+ * See if our compiler is known to support flexible array members.
+ */
+#ifndef GIT_FLEX_ARRAY
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define GIT_FLEX_ARRAY /* empty */
+# elif defined(__GNUC__)
+# if (__GNUC__ >= 3)
+# define GIT_FLEX_ARRAY /* empty */
+# else
+# define GIT_FLEX_ARRAY 0 /* older GNU extension */
+# endif
+# endif
+
+/* Default to safer but a bit wasteful traditional style */
+# ifndef GIT_FLEX_ARRAY
+# define GIT_FLEX_ARRAY 1
+# endif
+#endif
+
+#ifdef __GNUC__
+# define GIT_TYPEOF(x) (__typeof__(x))
+#else
+# define GIT_TYPEOF(x)
+#endif
+
+#if defined(__GNUC__)
+# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size)))
+#elif defined(_MSC_VER)
+# define GIT_ALIGN(x,size) __declspec(align(size)) x
+#else
+# define GIT_ALIGN(x,size) x
+#endif
+
+#define GIT_UNUSED(x) ((void)(x))
+
+/* Define the printf format specifer to use for size_t output */
+#if defined(_MSC_VER) || defined(__MINGW32__)
+# define PRIuZ "Iu"
+# define PRIxZ "Ix"
+# define PRIdZ "Id"
+#else
+# define PRIuZ "zu"
+# define PRIxZ "zx"
+# define PRIdZ "zd"
+#endif
+
+/* Micosoft Visual C/C++ */
+#if defined(_MSC_VER)
+/* disable "deprecated function" warnings */
+# pragma warning ( disable : 4996 )
+/* disable "conditional expression is constant" level 4 warnings */
+# pragma warning ( disable : 4127 )
+#endif
+
+#if defined (_MSC_VER)
+ typedef unsigned char bool;
+# ifndef true
+# define true 1
+# endif
+# ifndef false
+# define false 0
+# endif
+#else
+# include <stdbool.h>
+#endif
+
+#ifndef va_copy
+# ifdef __va_copy
+# define va_copy(dst, src) __va_copy(dst, src)
+# else
+# define va_copy(dst, src) ((dst) = (src))
+# endif
+#endif
+
+#endif /* INCLUDE_compat_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "checkout.h"
+
+#include "git2/repository.h"
+#include "git2/refs.h"
+#include "git2/tree.h"
+#include "git2/blob.h"
+#include "git2/config.h"
+#include "git2/diff.h"
+#include "git2/submodule.h"
+#include "git2/sys/index.h"
+#include "git2/sys/filter.h"
+#include "git2/merge.h"
+
+#include "refs.h"
+#include "repository.h"
+#include "index.h"
+#include "filter.h"
+#include "blob.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "pathspec.h"
+#include "buf_text.h"
+#include "diff_xdiff.h"
+#include "path.h"
+#include "attr.h"
+#include "pool.h"
+#include "strmap.h"
+
+GIT__USE_STRMAP
+
+/* See docs/checkout-internals.md for more information */
+
+enum {
+ CHECKOUT_ACTION__NONE = 0,
+ CHECKOUT_ACTION__REMOVE = 1,
+ CHECKOUT_ACTION__UPDATE_BLOB = 2,
+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
+ CHECKOUT_ACTION__CONFLICT = 8,
+ CHECKOUT_ACTION__REMOVE_CONFLICT = 16,
+ CHECKOUT_ACTION__UPDATE_CONFLICT = 32,
+ CHECKOUT_ACTION__MAX = 32,
+ CHECKOUT_ACTION__DEFER_REMOVE = 64,
+ CHECKOUT_ACTION__REMOVE_AND_UPDATE =
+ (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
+};
+
+typedef struct {
+ git_repository *repo;
+ git_iterator *target;
+ git_diff *diff;
+ git_checkout_options opts;
+ bool opts_free_baseline;
+ char *pfx;
+ git_index *index;
+ git_pool pool;
+ git_vector removes;
+ git_vector remove_conflicts;
+ git_vector update_conflicts;
+ git_vector *update_reuc;
+ git_vector *update_names;
+ git_buf target_path;
+ size_t target_len;
+ git_buf tmp;
+ unsigned int strategy;
+ int can_symlink;
+ bool reload_submodules;
+ size_t total_steps;
+ size_t completed_steps;
+ git_checkout_perfdata perfdata;
+ git_strmap *mkdir_map;
+ git_attr_session attr_session;
+} checkout_data;
+
+typedef struct {
+ const git_index_entry *ancestor;
+ const git_index_entry *ours;
+ const git_index_entry *theirs;
+
+ int name_collision:1,
+ directoryfile:1,
+ one_to_two:1,
+ binary:1,
+ submodule:1;
+} checkout_conflictdata;
+
+static int checkout_notify(
+ checkout_data *data,
+ git_checkout_notify_t why,
+ const git_diff_delta *delta,
+ const git_index_entry *wditem)
+{
+ git_diff_file wdfile;
+ const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;
+ const char *path = NULL;
+
+ if (!data->opts.notify_cb ||
+ (why & data->opts.notify_flags) == 0)
+ return 0;
+
+ if (wditem) {
+ memset(&wdfile, 0, sizeof(wdfile));
+
+ git_oid_cpy(&wdfile.id, &wditem->id);
+ wdfile.path = wditem->path;
+ wdfile.size = wditem->file_size;
+ wdfile.flags = GIT_DIFF_FLAG_VALID_ID;
+ wdfile.mode = wditem->mode;
+
+ workdir = &wdfile;
+
+ path = wditem->path;
+ }
+
+ if (delta) {
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ default:
+ baseline = &delta->old_file;
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_UNREADABLE:
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_DELETED:
+ baseline = &delta->old_file;
+ break;
+ }
+
+ path = delta->old_file.path;
+ }
+
+ {
+ int error = data->opts.notify_cb(
+ why, path, baseline, target, workdir, data->opts.notify_payload);
+
+ return giterr_set_after_callback_function(
+ error, "git_checkout notification");
+ }
+}
+
+GIT_INLINE(bool) is_workdir_base_or_new(
+ const git_oid *workdir_id,
+ const git_diff_file *baseitem,
+ const git_diff_file *newitem)
+{
+ return (git_oid__cmp(&baseitem->id, workdir_id) == 0 ||
+ git_oid__cmp(&newitem->id, workdir_id) == 0);
+}
+
+static bool checkout_is_workdir_modified(
+ checkout_data *data,
+ const git_diff_file *baseitem,
+ const git_diff_file *newitem,
+ const git_index_entry *wditem)
+{
+ git_oid oid;
+ const git_index_entry *ie;
+
+ /* handle "modified" submodule */
+ if (wditem->mode == GIT_FILEMODE_COMMIT) {
+ git_submodule *sm;
+ unsigned int sm_status = 0;
+ const git_oid *sm_oid = NULL;
+ bool rval = false;
+
+ if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) {
+ giterr_clear();
+ return true;
+ }
+
+ if (git_submodule_status(&sm_status, data->repo, wditem->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) < 0 ||
+ GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ rval = true;
+ else if ((sm_oid = git_submodule_wd_id(sm)) == NULL)
+ rval = false;
+ else
+ rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0);
+
+ git_submodule_free(sm);
+ return rval;
+ }
+
+ /* Look at the cache to decide if the workdir is modified. If not,
+ * we can simply compare the oid in the cache to the baseitem instead
+ * of hashing the file. If so, we allow the checkout to proceed if the
+ * oid is identical (ie, the staged item is what we're trying to check
+ * out.)
+ */
+ if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) {
+ if (git_index_time_eq(&wditem->mtime, &ie->mtime) &&
+ wditem->file_size == ie->file_size)
+ return !is_workdir_base_or_new(&ie->id, baseitem, newitem);
+ }
+
+ /* depending on where base is coming from, we may or may not know
+ * the actual size of the data, so we can't rely on this shortcut.
+ */
+ if (baseitem->size && wditem->file_size != baseitem->size)
+ return true;
+
+ /* if the workdir item is a directory, it cannot be a modified file */
+ if (S_ISDIR(wditem->mode))
+ return false;
+
+ if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0)
+ return false;
+
+ /* Allow the checkout if the workdir is not modified *or* if the checkout
+ * target's contents are already in the working directory.
+ */
+ return !is_workdir_base_or_new(&oid, baseitem, newitem);
+}
+
+#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
+ ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)
+
+static int checkout_action_common(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ *action = (*action & ~CHECKOUT_ACTION__REMOVE);
+
+ if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->new_file.mode))
+ *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
+
+ /* to "update" a symlink, we must remove the old one first */
+ if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL)
+ *action |= CHECKOUT_ACTION__REMOVE;
+
+ /* if the file is on disk and doesn't match our mode, force update */
+ if (wd &&
+ GIT_PERMS_IS_EXEC(wd->mode) !=
+ GIT_PERMS_IS_EXEC(delta->new_file.mode))
+ *action |= CHECKOUT_ACTION__REMOVE;
+
+ notify = GIT_CHECKOUT_NOTIFY_UPDATED;
+ }
+
+ if ((*action & CHECKOUT_ACTION__CONFLICT) != 0)
+ notify = GIT_CHECKOUT_NOTIFY_CONFLICT;
+
+ return checkout_notify(data, notify, delta, wd);
+}
+
+static int checkout_action_no_wd(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta)
+{
+ int error = 0;
+
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 12 */
+ error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL);
+ if (error)
+ return error;
+ *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_DELETED: /* case 8 or 25 */
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, NULL);
+}
+
+static int checkout_target_fullpath(
+ git_buf **out, checkout_data *data, const char *path)
+{
+ git_buf_truncate(&data->target_path, data->target_len);
+
+ if (path && git_buf_puts(&data->target_path, path) < 0)
+ return -1;
+
+ *out = &data->target_path;
+
+ return 0;
+}
+
+static bool wd_item_is_removable(
+ checkout_data *data, const git_index_entry *wd)
+{
+ git_buf *full;
+
+ if (wd->mode != GIT_FILEMODE_TREE)
+ return true;
+
+ if (checkout_target_fullpath(&full, data, wd->path) < 0)
+ return false;
+
+ return !full || !git_path_contains(full, DOT_GIT);
+}
+
+static int checkout_queue_remove(checkout_data *data, const char *path)
+{
+ char *copy = git_pool_strdup(&data->pool, path);
+ GITERR_CHECK_ALLOC(copy);
+ return git_vector_insert(&data->removes, copy);
+}
+
+/* note that this advances the iterator over the wd item */
+static int checkout_action_wd_only(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry **wditem,
+ git_vector *pathspec)
+{
+ int error = 0;
+ bool remove = false;
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+ const git_index_entry *wd = *wditem;
+
+ if (!git_pathspec__match(
+ pathspec, wd->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return git_iterator_advance(wditem, workdir);
+
+ /* check if item is tracked in the index but not in the checkout diff */
+ if (data->index != NULL) {
+ size_t pos;
+
+ error = git_index__find_pos(
+ &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY);
+
+ if (wd->mode != GIT_FILEMODE_TREE) {
+ if (!error) { /* found by git_index__find_pos call */
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ } else if (error != GIT_ENOTFOUND)
+ return error;
+ else
+ error = 0; /* git_index__find_pos does not set error msg */
+ } else {
+ /* for tree entries, we have to see if there are any index
+ * entries that are contained inside that tree
+ */
+ const git_index_entry *e = git_index_get_byindex(data->index, pos);
+
+ if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) {
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ }
+ }
+ }
+
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE) {
+ /* if we found something in the index, notify and advance */
+ if ((error = checkout_notify(data, notify, NULL, wd)) != 0)
+ return error;
+
+ if (remove && wd_item_is_removable(data, wd))
+ error = checkout_queue_remove(data, wd->path);
+
+ if (!error)
+ error = git_iterator_advance(wditem, workdir);
+ } else {
+ /* untracked or ignored - can't know which until we advance through */
+ bool over = false, removable = wd_item_is_removable(data, wd);
+ git_iterator_status_t untracked_state;
+
+ /* copy the entry for issuing notification callback later */
+ git_index_entry saved_wd = *wd;
+ git_buf_sets(&data->tmp, wd->path);
+ saved_wd.path = data->tmp.ptr;
+
+ error = git_iterator_advance_over(
+ wditem, &untracked_state, workdir);
+ if (error == GIT_ITEROVER)
+ over = true;
+ else if (error < 0)
+ return error;
+
+ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) {
+ notify = GIT_CHECKOUT_NOTIFY_IGNORED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
+ } else {
+ notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
+ }
+
+ if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0)
+ return error;
+
+ if (remove && removable)
+ error = checkout_queue_remove(data, saved_wd.path);
+
+ if (!error && over) /* restore ITEROVER if needed */
+ error = GIT_ITEROVER;
+ }
+
+ return error;
+}
+
+static bool submodule_is_config_only(
+ checkout_data *data,
+ const char *path)
+{
+ git_submodule *sm = NULL;
+ unsigned int sm_loc = 0;
+ bool rval = false;
+
+ if (git_submodule_lookup(&sm, data->repo, path) < 0)
+ return true;
+
+ if (git_submodule_location(&sm_loc, sm) < 0 ||
+ sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG)
+ rval = true;
+
+ git_submodule_free(sm);
+
+ return rval;
+}
+
+static bool checkout_is_empty_dir(checkout_data *data, const char *path)
+{
+ git_buf *fullpath;
+
+ if (checkout_target_fullpath(&fullpath, data, path) < 0)
+ return false;
+
+ return git_path_is_empty_dir(fullpath->ptr);
+}
+
+static int checkout_action_with_wd(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
+ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) {
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) );
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
+ }
+ break;
+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
+ if (git_iterator_current_is_ignored(workdir))
+ *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB);
+ else
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
+ if (wd->mode != GIT_FILEMODE_COMMIT &&
+ checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ if (wd->mode == GIT_FILEMODE_TREE)
+ /* either deleting items in old tree will delete the wd dir,
+ * or we'll get a conflict when we attempt blob update...
+ */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else if (wd->mode == GIT_FILEMODE_COMMIT) {
+ /* workdir is possibly a "phantom" submodule - treat as a
+ * tree if the only submodule info came from the config
+ */
+ if (submodule_is_config_only(data, wd->path))
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ } else
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ }
+ else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
+
+ /* don't update if the typechange is to a tree */
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action_with_wd_blocker(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ /* should show delta as dirty / deleted */
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) );
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED:
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ /* not 100% certain about this... */
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action_with_wd_dir(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL));
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd));
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
+ break;
+ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
+ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ /* expected submodule (and maybe found one) */;
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ *action = git_iterator_current_is_ignored(workdir) ?
+ CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) :
+ CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
+ if (delta->old_file.mode != GIT_FILEMODE_TREE)
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd));
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ /* For typechange from dir, remove dir and add blob, but it is
+ * not safe to remove dir if it contains modified files.
+ * However, safely removing child files will remove the parent
+ * directory if is it left empty, so we can defer removing the
+ * dir and it will succeed if no children are left.
+ */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ }
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ /* For typechange to dir, dir is already created so no action */
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action_with_wd_dir_empty(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta)
+{
+ int error = checkout_action_no_wd(action, data, delta);
+
+ /* We can always safely remove an empty directory. */
+ if (error == 0 && *action != CHECKOUT_ACTION__NONE)
+ *action |= CHECKOUT_ACTION__REMOVE;
+
+ return error;
+}
+
+static int checkout_action(
+ int *action,
+ checkout_data *data,
+ git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry **wditem,
+ git_vector *pathspec)
+{
+ int cmp = -1, error;
+ int (*strcomp)(const char *, const char *) = data->diff->strcomp;
+ int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
+ int (*advance)(const git_index_entry **, git_iterator *) = NULL;
+
+ /* move workdir iterator to follow along with deltas */
+
+ while (1) {
+ const git_index_entry *wd = *wditem;
+
+ if (!wd)
+ return checkout_action_no_wd(action, data, delta);
+
+ cmp = strcomp(wd->path, delta->old_file.path);
+
+ /* 1. wd before delta ("a/a" before "a/b")
+ * 2. wd prefixes delta & should expand ("a/" before "a/b")
+ * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")
+ * 4. wd equals delta ("a/b" and "a/b")
+ * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")
+ * 6. wd after delta ("a/c" after "a/b")
+ */
+
+ if (cmp < 0) {
+ cmp = pfxcomp(delta->old_file.path, wd->path);
+
+ if (cmp == 0) {
+ if (wd->mode == GIT_FILEMODE_TREE) {
+ /* case 2 - entry prefixed by workdir tree */
+ error = git_iterator_advance_into(wditem, workdir);
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ continue;
+ }
+
+ /* case 3 maybe - wd contains non-dir where dir expected */
+ if (delta->old_file.path[strlen(wd->path)] == '/') {
+ error = checkout_action_with_wd_blocker(
+ action, data, delta, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+ }
+
+ /* case 1 - handle wd item (if it matches pathspec) */
+ error = checkout_action_wd_only(data, workdir, wditem, pathspec);
+ if (error && error != GIT_ITEROVER)
+ goto done;
+ continue;
+ }
+
+ if (cmp == 0) {
+ /* case 4 */
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+
+ cmp = pfxcomp(wd->path, delta->old_file.path);
+
+ if (cmp == 0) { /* case 5 */
+ if (wd->path[strlen(delta->old_file.path)] != '/')
+ return checkout_action_no_wd(action, data, delta);
+
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance_into;
+ goto done;
+ }
+
+ if (delta->new_file.mode == GIT_FILEMODE_TREE ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ {
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+ }
+
+ return checkout_is_empty_dir(data, wd->path) ?
+ checkout_action_with_wd_dir_empty(action, data, delta) :
+ checkout_action_with_wd_dir(action, data, delta, workdir, wd);
+ }
+
+ /* case 6 - wd is after delta */
+ return checkout_action_no_wd(action, data, delta);
+ }
+
+done:
+ if (!error && advance != NULL &&
+ (error = advance(wditem, workdir)) < 0) {
+ *wditem = NULL;
+ if (error == GIT_ITEROVER)
+ error = 0;
+ }
+
+ return error;
+}
+
+static int checkout_remaining_wd_items(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry *wd,
+ git_vector *spec)
+{
+ int error = 0;
+
+ while (wd && !error)
+ error = checkout_action_wd_only(data, workdir, &wd, spec);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+GIT_INLINE(int) checkout_idxentry_cmp(
+ const git_index_entry *a,
+ const git_index_entry *b)
+{
+ if (!a && !b)
+ return 0;
+ else if (!a && b)
+ return -1;
+ else if(a && !b)
+ return 1;
+ else
+ return strcmp(a->path, b->path);
+}
+
+static int checkout_conflictdata_cmp(const void *a, const void *b)
+{
+ const checkout_conflictdata *ca = a;
+ const checkout_conflictdata *cb = b;
+ int diff;
+
+ if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 &&
+ (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0)
+ diff = checkout_idxentry_cmp(ca->theirs, cb->theirs);
+
+ return diff;
+}
+
+int checkout_conflictdata_empty(
+ const git_vector *conflicts, size_t idx, void *payload)
+{
+ checkout_conflictdata *conflict;
+
+ GIT_UNUSED(payload);
+
+ if ((conflict = git_vector_get(conflicts, idx)) == NULL)
+ return -1;
+
+ if (conflict->ancestor || conflict->ours || conflict->theirs)
+ return 0;
+
+ git__free(conflict);
+ return 1;
+}
+
+GIT_INLINE(bool) conflict_pathspec_match(
+ checkout_data *data,
+ git_iterator *workdir,
+ git_vector *pathspec,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs)
+{
+ /* if the pathspec matches ours *or* theirs, proceed */
+ if (ours && git_pathspec__match(pathspec, ours->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ if (theirs && git_pathspec__match(pathspec, theirs->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ if (ancestor && git_pathspec__match(pathspec, ancestor->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict)
+{
+ conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) ||
+ (conflict->ours && S_ISGITLINK(conflict->ours->mode)) ||
+ (conflict->theirs && S_ISGITLINK(conflict->theirs->mode)));
+ return 0;
+}
+
+GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict)
+{
+ git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL;
+ int error = 0;
+
+ if (conflict->submodule)
+ return 0;
+
+ if (conflict->ancestor) {
+ if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(ancestor_blob);
+ }
+
+ if (!conflict->binary && conflict->ours) {
+ if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(our_blob);
+ }
+
+ if (!conflict->binary && conflict->theirs) {
+ if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(their_blob);
+ }
+
+done:
+ git_blob_free(ancestor_blob);
+ git_blob_free(our_blob);
+ git_blob_free(their_blob);
+
+ return error;
+}
+
+static int checkout_conflict_append_update(
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ void *payload)
+{
+ checkout_data *data = payload;
+ checkout_conflictdata *conflict;
+ int error;
+
+ conflict = git__calloc(1, sizeof(checkout_conflictdata));
+ GITERR_CHECK_ALLOC(conflict);
+
+ conflict->ancestor = ancestor;
+ conflict->ours = ours;
+ conflict->theirs = theirs;
+
+ if ((error = checkout_conflict_detect_submodule(conflict)) < 0 ||
+ (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0)
+ {
+ git__free(conflict);
+ return error;
+ }
+
+ if (git_vector_insert(&data->update_conflicts, conflict))
+ return -1;
+
+ return 0;
+}
+
+static int checkout_conflicts_foreach(
+ checkout_data *data,
+ git_index *index,
+ git_iterator *workdir,
+ git_vector *pathspec,
+ int (*cb)(const git_index_entry *, const git_index_entry *, const git_index_entry *, void *),
+ void *payload)
+{
+ git_index_conflict_iterator *iterator = NULL;
+ const git_index_entry *ancestor, *ours, *theirs;
+ int error = 0;
+
+ if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0)
+ goto done;
+
+ /* Collect the conflicts */
+ while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) {
+ if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs))
+ continue;
+
+ if ((error = cb(ancestor, ours, theirs, payload)) < 0)
+ goto done;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+done:
+ git_index_conflict_iterator_free(iterator);
+
+ return error;
+}
+
+static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec)
+{
+ git_index *index;
+
+ /* Only write conficts from sources that have them: indexes. */
+ if ((index = git_iterator_index(data->target)) == NULL)
+ return 0;
+
+ data->update_conflicts._cmp = checkout_conflictdata_cmp;
+
+ if (checkout_conflicts_foreach(data, index, workdir, pathspec, checkout_conflict_append_update, data) < 0)
+ return -1;
+
+ /* Collect the REUC and NAME entries */
+ data->update_reuc = &index->reuc;
+ data->update_names = &index->names;
+
+ return 0;
+}
+
+GIT_INLINE(int) checkout_conflicts_cmp_entry(
+ const char *path,
+ const git_index_entry *entry)
+{
+ return strcmp((const char *)path, entry->path);
+}
+
+static int checkout_conflicts_cmp_ancestor(const void *p, const void *c)
+{
+ const char *path = p;
+ const checkout_conflictdata *conflict = c;
+
+ if (!conflict->ancestor)
+ return 1;
+
+ return checkout_conflicts_cmp_entry(path, conflict->ancestor);
+}
+
+static checkout_conflictdata *checkout_conflicts_search_ancestor(
+ checkout_data *data,
+ const char *path)
+{
+ size_t pos;
+
+ if (git_vector_bsearch2(&pos, &data->update_conflicts, checkout_conflicts_cmp_ancestor, path) < 0)
+ return NULL;
+
+ return git_vector_get(&data->update_conflicts, pos);
+}
+
+static checkout_conflictdata *checkout_conflicts_search_branch(
+ checkout_data *data,
+ const char *path)
+{
+ checkout_conflictdata *conflict;
+ size_t i;
+
+ git_vector_foreach(&data->update_conflicts, i, conflict) {
+ int cmp = -1;
+
+ if (conflict->ancestor)
+ break;
+
+ if (conflict->ours)
+ cmp = checkout_conflicts_cmp_entry(path, conflict->ours);
+ else if (conflict->theirs)
+ cmp = checkout_conflicts_cmp_entry(path, conflict->theirs);
+
+ if (cmp == 0)
+ return conflict;
+ }
+
+ return NULL;
+}
+
+static int checkout_conflicts_load_byname_entry(
+ checkout_conflictdata **ancestor_out,
+ checkout_conflictdata **ours_out,
+ checkout_conflictdata **theirs_out,
+ checkout_data *data,
+ const git_index_name_entry *name_entry)
+{
+ checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL;
+ int error = 0;
+
+ *ancestor_out = NULL;
+ *ours_out = NULL;
+ *theirs_out = NULL;
+
+ if (!name_entry->ancestor) {
+ giterr_set(GITERR_INDEX, "A NAME entry exists without an ancestor");
+ error = -1;
+ goto done;
+ }
+
+ if (!name_entry->ours && !name_entry->theirs) {
+ giterr_set(GITERR_INDEX, "A NAME entry exists without an ours or theirs");
+ error = -1;
+ goto done;
+ }
+
+ if ((ancestor = checkout_conflicts_search_ancestor(data,
+ name_entry->ancestor)) == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced ancestor entry '%s' which does not exist in the main index",
+ name_entry->ancestor);
+ error = -1;
+ goto done;
+ }
+
+ if (name_entry->ours) {
+ if (strcmp(name_entry->ancestor, name_entry->ours) == 0)
+ ours = ancestor;
+ else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL ||
+ ours->ours == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced our entry '%s' which does not exist in the main index",
+ name_entry->ours);
+ error = -1;
+ goto done;
+ }
+ }
+
+ if (name_entry->theirs) {
+ if (strcmp(name_entry->ancestor, name_entry->theirs) == 0)
+ theirs = ancestor;
+ else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0)
+ theirs = ours;
+ else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL ||
+ theirs->theirs == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced their entry '%s' which does not exist in the main index",
+ name_entry->theirs);
+ error = -1;
+ goto done;
+ }
+ }
+
+ *ancestor_out = ancestor;
+ *ours_out = ours;
+ *theirs_out = theirs;
+
+done:
+ return error;
+}
+
+static int checkout_conflicts_coalesce_renames(
+ checkout_data *data)
+{
+ git_index *index;
+ const git_index_name_entry *name_entry;
+ checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict;
+ size_t i, names;
+ int error = 0;
+
+ if ((index = git_iterator_index(data->target)) == NULL)
+ return 0;
+
+ /* Juggle entries based on renames */
+ names = git_index_name_entrycount(index);
+
+ for (i = 0; i < names; i++) {
+ name_entry = git_index_name_get_byindex(index, i);
+
+ if ((error = checkout_conflicts_load_byname_entry(
+ &ancestor_conflict, &our_conflict, &their_conflict,
+ data, name_entry)) < 0)
+ goto done;
+
+ if (our_conflict && our_conflict != ancestor_conflict) {
+ ancestor_conflict->ours = our_conflict->ours;
+ our_conflict->ours = NULL;
+
+ if (our_conflict->theirs)
+ our_conflict->name_collision = 1;
+
+ if (our_conflict->name_collision)
+ ancestor_conflict->name_collision = 1;
+ }
+
+ if (their_conflict && their_conflict != ancestor_conflict) {
+ ancestor_conflict->theirs = their_conflict->theirs;
+ their_conflict->theirs = NULL;
+
+ if (their_conflict->ours)
+ their_conflict->name_collision = 1;
+
+ if (their_conflict->name_collision)
+ ancestor_conflict->name_collision = 1;
+ }
+
+ if (our_conflict && our_conflict != ancestor_conflict &&
+ their_conflict && their_conflict != ancestor_conflict)
+ ancestor_conflict->one_to_two = 1;
+ }
+
+ git_vector_remove_matching(
+ &data->update_conflicts, checkout_conflictdata_empty, NULL);
+
+done:
+ return error;
+}
+
+static int checkout_conflicts_mark_directoryfile(
+ checkout_data *data)
+{
+ git_index *index;
+ checkout_conflictdata *conflict;
+ const git_index_entry *entry;
+ size_t i, j, len;
+ const char *path;
+ int prefixed, error = 0;
+
+ if ((index = git_iterator_index(data->target)) == NULL)
+ return 0;
+
+ len = git_index_entrycount(index);
+
+ /* Find d/f conflicts */
+ git_vector_foreach(&data->update_conflicts, i, conflict) {
+ if ((conflict->ours && conflict->theirs) ||
+ (!conflict->ours && !conflict->theirs))
+ continue;
+
+ path = conflict->ours ?
+ conflict->ours->path : conflict->theirs->path;
+
+ if ((error = git_index_find(&j, index, path)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_INDEX,
+ "Index inconsistency, could not find entry for expected conflict '%s'", path);
+
+ goto done;
+ }
+
+ for (; j < len; j++) {
+ if ((entry = git_index_get_byindex(index, j)) == NULL) {
+ giterr_set(GITERR_INDEX,
+ "Index inconsistency, truncated index while loading expected conflict '%s'", path);
+ error = -1;
+ goto done;
+ }
+
+ prefixed = git_path_equal_or_prefixed(path, entry->path, NULL);
+
+ if (prefixed == GIT_PATH_EQUAL)
+ continue;
+
+ if (prefixed == GIT_PATH_PREFIX)
+ conflict->directoryfile = 1;
+
+ break;
+ }
+ }
+
+done:
+ return error;
+}
+
+static int checkout_get_update_conflicts(
+ checkout_data *data,
+ git_iterator *workdir,
+ git_vector *pathspec)
+{
+ int error = 0;
+
+ if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED)
+ return 0;
+
+ if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 ||
+ (error = checkout_conflicts_coalesce_renames(data)) < 0 ||
+ (error = checkout_conflicts_mark_directoryfile(data)) < 0)
+ goto done;
+
+done:
+ return error;
+}
+
+static int checkout_conflict_append_remove(
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ void *payload)
+{
+ checkout_data *data = payload;
+ const char *name;
+
+ assert(ancestor || ours || theirs);
+
+ if (ancestor)
+ name = git__strdup(ancestor->path);
+ else if (ours)
+ name = git__strdup(ours->path);
+ else if (theirs)
+ name = git__strdup(theirs->path);
+ else
+ abort();
+
+ GITERR_CHECK_ALLOC(name);
+
+ return git_vector_insert(&data->remove_conflicts, (char *)name);
+}
+
+static int checkout_get_remove_conflicts(
+ checkout_data *data,
+ git_iterator *workdir,
+ git_vector *pathspec)
+{
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
+ return 0;
+
+ return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data);
+}
+
+static int checkout_verify_paths(
+ git_repository *repo,
+ int action,
+ git_diff_delta *delta)
+{
+ unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS;
+
+ if (action & CHECKOUT_ACTION__REMOVE) {
+ if (!git_path_isvalid(repo, delta->old_file.path, flags)) {
+ giterr_set(GITERR_CHECKOUT, "Cannot remove invalid path '%s'", delta->old_file.path);
+ return -1;
+ }
+ }
+
+ if (action & ~CHECKOUT_ACTION__REMOVE) {
+ if (!git_path_isvalid(repo, delta->new_file.path, flags)) {
+ giterr_set(GITERR_CHECKOUT, "Cannot checkout to invalid path '%s'", delta->new_file.path);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_get_actions(
+ uint32_t **actions_ptr,
+ size_t **counts_ptr,
+ checkout_data *data,
+ git_iterator *workdir)
+{
+ int error = 0, act;
+ const git_index_entry *wditem;
+ git_vector pathspec = GIT_VECTOR_INIT, *deltas;
+ git_pool pathpool;
+ git_diff_delta *delta;
+ size_t i, *counts = NULL;
+ uint32_t *actions = NULL;
+
+ git_pool_init(&pathpool, 1);
+
+ if (data->opts.paths.count > 0 &&
+ git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0)
+ return -1;
+
+ if ((error = git_iterator_current(&wditem, workdir)) < 0 &&
+ error != GIT_ITEROVER)
+ goto fail;
+
+ deltas = &data->diff->deltas;
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(
+ deltas->length ? deltas->length : 1, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
+
+ git_vector_foreach(deltas, i, delta) {
+ if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0)
+ error = checkout_verify_paths(data->repo, act, delta);
+
+ if (error != 0)
+ goto fail;
+
+ actions[i] = act;
+
+ if (act & CHECKOUT_ACTION__REMOVE)
+ counts[CHECKOUT_ACTION__REMOVE]++;
+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)
+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__CONFLICT)
+ counts[CHECKOUT_ACTION__CONFLICT]++;
+ }
+
+ error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec);
+ if (error)
+ goto fail;
+
+ counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;
+
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%"PRIuZ" %s checkout",
+ counts[CHECKOUT_ACTION__CONFLICT],
+ counts[CHECKOUT_ACTION__CONFLICT] == 1 ?
+ "conflict prevents" : "conflicts prevent");
+ error = GIT_ECONFLICT;
+ goto fail;
+ }
+
+
+ if ((error = checkout_get_remove_conflicts(data, workdir, &pathspec)) < 0 ||
+ (error = checkout_get_update_conflicts(data, workdir, &pathspec)) < 0)
+ goto fail;
+
+ counts[CHECKOUT_ACTION__REMOVE_CONFLICT] = git_vector_length(&data->remove_conflicts);
+ counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->update_conflicts);
+
+ git_pathspec__vfree(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_pathspec__vfree(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return error;
+}
+
+static bool should_remove_existing(checkout_data *data)
+{
+ int ignorecase;
+
+ if (git_repository__cvar(&ignorecase, data->repo, GIT_CVAR_IGNORECASE) < 0) {
+ ignorecase = 0;
+ }
+
+ return (ignorecase &&
+ (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0);
+}
+
+#define MKDIR_NORMAL \
+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR
+#define MKDIR_REMOVE_EXISTING \
+ MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS
+
+static int checkout_mkdir(
+ checkout_data *data,
+ const char *path,
+ const char *base,
+ mode_t mode,
+ unsigned int flags)
+{
+ struct git_futils_mkdir_options mkdir_opts = {0};
+ int error;
+
+ mkdir_opts.dir_map = data->mkdir_map;
+ mkdir_opts.pool = &data->pool;
+
+ error = git_futils_mkdir_relative(
+ path, base, mode, flags, &mkdir_opts);
+
+ data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls;
+ data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls;
+ data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls;
+
+ return error;
+}
+
+static int mkpath2file(
+ checkout_data *data, const char *path, unsigned int mode)
+{
+ struct stat st;
+ bool remove_existing = should_remove_existing(data);
+ unsigned int flags =
+ (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) |
+ GIT_MKDIR_SKIP_LAST;
+ int error;
+
+ if ((error = checkout_mkdir(
+ data, path, data->opts.target_directory, mode, flags)) < 0)
+ return error;
+
+ if (remove_existing) {
+ data->perfdata.stat_calls++;
+
+ if (p_lstat(path, &st) == 0) {
+
+ /* Some file, symlink or folder already exists at this name.
+ * We would have removed it in remove_the_old unless we're on
+ * a case inensitive filesystem (or the user has asked us not
+ * to). Remove the similarly named file to write the new.
+ */
+ error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES);
+ } else if (errno != ENOENT) {
+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ return GIT_EEXISTS;
+ } else {
+ giterr_clear();
+ }
+ }
+
+ return error;
+}
+
+struct checkout_stream {
+ git_writestream base;
+ const char *path;
+ int fd;
+ int open;
+};
+
+static int checkout_stream_write(
+ git_writestream *s, const char *buffer, size_t len)
+{
+ struct checkout_stream *stream = (struct checkout_stream *)s;
+ int ret;
+
+ if ((ret = p_write(stream->fd, buffer, len)) < 0)
+ giterr_set(GITERR_OS, "Could not write to '%s'", stream->path);
+
+ return ret;
+}
+
+static int checkout_stream_close(git_writestream *s)
+{
+ struct checkout_stream *stream = (struct checkout_stream *)s;
+ assert(stream && stream->open);
+
+ stream->open = 0;
+ return p_close(stream->fd);
+}
+
+static void checkout_stream_free(git_writestream *s)
+{
+ GIT_UNUSED(s);
+}
+
+static int blob_content_to_file(
+ checkout_data *data,
+ struct stat *st,
+ git_blob *blob,
+ const char *path,
+ const char *hint_path,
+ mode_t entry_filemode)
+{
+ int flags = data->opts.file_open_flags;
+ mode_t file_mode = data->opts.file_mode ?
+ data->opts.file_mode : entry_filemode;
+ git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
+ struct checkout_stream writer;
+ mode_t mode;
+ git_filter_list *fl = NULL;
+ int fd;
+ int error = 0;
+
+ if (hint_path == NULL)
+ hint_path = path;
+
+ if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
+ return error;
+
+ if (flags <= 0)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+ if (!(mode = file_mode))
+ mode = GIT_FILEMODE_BLOB;
+
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ filter_opts.attr_session = &data->attr_session;
+ filter_opts.temp_buf = &data->tmp;
+
+ if (!data->opts.disable_filters &&
+ (error = git_filter_list__load_ext(
+ &fl, data->repo, blob, hint_path,
+ GIT_FILTER_TO_WORKTREE, &filter_opts))) {
+ p_close(fd);
+ return error;
+ }
+
+ /* setup the writer */
+ memset(&writer, 0, sizeof(struct checkout_stream));
+ writer.base.write = checkout_stream_write;
+ writer.base.close = checkout_stream_close;
+ writer.base.free = checkout_stream_free;
+ writer.path = path;
+ writer.fd = fd;
+ writer.open = 1;
+
+ error = git_filter_list_stream_blob(fl, blob, &writer.base);
+
+ assert(writer.open == 0);
+
+ git_filter_list_free(fl);
+
+ if (error < 0)
+ return error;
+
+ if (st) {
+ data->perfdata.stat_calls++;
+
+ if ((error = p_stat(path, st)) < 0) {
+ giterr_set(GITERR_OS, "Error statting '%s'", path);
+ return error;
+ }
+
+ st->st_mode = entry_filemode;
+ }
+
+ return 0;
+}
+
+static int blob_content_to_link(
+ checkout_data *data,
+ struct stat *st,
+ git_blob *blob,
+ const char *path)
+{
+ git_buf linktarget = GIT_BUF_INIT;
+ int error;
+
+ if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
+ return error;
+
+ if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
+ return error;
+
+ if (data->can_symlink) {
+ if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
+ giterr_set(GITERR_OS, "Could not create symlink %s", path);
+ } else {
+ error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
+ }
+
+ if (!error) {
+ data->perfdata.stat_calls++;
+
+ if ((error = p_lstat(path, st)) < 0)
+ giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
+
+ st->st_mode = GIT_FILEMODE_LINK;
+ }
+
+ git_buf_free(&linktarget);
+
+ return error;
+}
+
+static int checkout_update_index(
+ checkout_data *data,
+ const git_diff_file *file,
+ struct stat *st)
+{
+ git_index_entry entry;
+
+ if (!data->index)
+ return 0;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = (char *)file->path; /* cast to prevent warning */
+ git_index_entry__init_from_stat(&entry, st, true);
+ git_oid_cpy(&entry.id, &file->id);
+
+ return git_index_add(data->index, &entry);
+}
+
+static int checkout_submodule_update_index(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ git_buf *fullpath;
+ struct stat st;
+
+ /* update the index unless prevented */
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
+ return 0;
+
+ if (checkout_target_fullpath(&fullpath, data, file->path) < 0)
+ return -1;
+
+ data->perfdata.stat_calls++;
+ if (p_stat(fullpath->ptr, &st) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
+ return GIT_ENOTFOUND;
+ }
+
+ st.st_mode = GIT_FILEMODE_COMMIT;
+
+ return checkout_update_index(data, file, &st);
+}
+
+static int checkout_submodule(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ bool remove_existing = should_remove_existing(data);
+ int error = 0;
+
+ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
+ if ((error = checkout_mkdir(
+ data,
+ file->path, data->opts.target_directory, data->opts.dir_mode,
+ remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0)
+ return error;
+
+ if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) {
+ /* I've observed repos with submodules in the tree that do not
+ * have a .gitmodules - core Git just makes an empty directory
+ */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return checkout_submodule_update_index(data, file);
+ }
+
+ return error;
+ }
+
+ /* TODO: Support checkout_strategy options. Two circumstances:
+ * 1 - submodule already checked out, but we need to move the HEAD
+ * to the new OID, or
+ * 2 - submodule not checked out and we should recursively check it out
+ *
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
+ */
+
+ return checkout_submodule_update_index(data, file);
+}
+
+static void report_progress(
+ checkout_data *data,
+ const char *path)
+{
+ if (data->opts.progress_cb)
+ data->opts.progress_cb(
+ path, data->completed_steps, data->total_steps,
+ data->opts.progress_payload);
+}
+
+static int checkout_safe_for_update_only(
+ checkout_data *data, const char *path, mode_t expected_mode)
+{
+ struct stat st;
+
+ data->perfdata.stat_calls++;
+
+ if (p_lstat(path, &st) < 0) {
+ /* if doesn't exist, then no error and no update */
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ /* otherwise, stat error and no update */
+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ return -1;
+ }
+
+ /* only safe for update if this is the same type of file */
+ if ((st.st_mode & ~0777) == (expected_mode & ~0777))
+ return 1;
+
+ return 0;
+}
+
+static int checkout_write_content(
+ checkout_data *data,
+ const git_oid *oid,
+ const char *full_path,
+ const char *hint_path,
+ unsigned int mode,
+ struct stat *st)
+{
+ int error = 0;
+ git_blob *blob;
+
+ if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0)
+ return error;
+
+ if (S_ISLNK(mode))
+ error = blob_content_to_link(data, st, blob, full_path);
+ else
+ error = blob_content_to_file(data, st, blob, full_path, hint_path, mode);
+
+ git_blob_free(blob);
+
+ /* if we try to create the blob and an existing directory blocks it from
+ * being written, then there must have been a typechange conflict in a
+ * parent directory - suppress the error and try to continue.
+ */
+ if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&
+ (error == GIT_ENOTFOUND || error == GIT_EEXISTS))
+ {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+}
+
+static int checkout_blob(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ git_buf *fullpath;
+ struct stat st;
+ int error = 0;
+
+ if (checkout_target_fullpath(&fullpath, data, file->path) < 0)
+ return -1;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
+ int rval = checkout_safe_for_update_only(
+ data, fullpath->ptr, file->mode);
+
+ if (rval <= 0)
+ return rval;
+ }
+
+ error = checkout_write_content(
+ data, &file->id, fullpath->ptr, NULL, file->mode, &st);
+
+ /* update the index unless prevented */
+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = checkout_update_index(data, file, &st);
+
+ /* update the submodule data if this was a new .gitmodules file */
+ if (!error && strcmp(file->path, ".gitmodules") == 0)
+ data->reload_submodules = true;
+
+ return error;
+}
+
+static int checkout_remove_the_old(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ const char *str;
+ size_t i;
+ git_buf *fullpath;
+ uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
+
+ if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES)
+ flg |= GIT_RMDIR_SKIP_NONEMPTY;
+
+ if (checkout_target_fullpath(&fullpath, data, NULL) < 0)
+ return -1;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {
+ error = git_futils_rmdir_r(
+ delta->old_file.path, fullpath->ptr, flg);
+
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+
+ if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 &&
+ (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ (void)git_index_remove(data->index, delta->old_file.path, 0);
+ }
+ }
+ }
+
+ git_vector_foreach(&data->removes, i, str) {
+ error = git_futils_rmdir_r(str, fullpath->ptr, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, str);
+
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ if (str[strlen(str) - 1] == '/')
+ (void)git_index_remove_directory(data->index, str, 0);
+ else
+ (void)git_index_remove(data->index, str, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_deferred_remove(git_repository *repo, const char *path)
+{
+#if 0
+ int error = git_futils_rmdir_r(
+ path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS);
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+#else
+ GIT_UNUSED(repo);
+ GIT_UNUSED(path);
+ assert(false);
+ return 0;
+#endif
+}
+
+static int checkout_create_the_new(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this had a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
+ error = checkout_blob(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_create_submodules(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this has a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
+ int error = checkout_submodule(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
+{
+ int error = 0;
+ git_reference *ref = NULL;
+ git_object *head;
+
+ if (!(error = git_repository_head(&ref, repo)) &&
+ !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
+ *out = (git_tree *)head;
+
+ git_reference_free(ref);
+
+ return error;
+}
+
+
+static int conflict_entry_name(
+ git_buf *out,
+ const char *side_name,
+ const char *filename)
+{
+ if (git_buf_puts(out, side_name) < 0 ||
+ git_buf_putc(out, ':') < 0 ||
+ git_buf_puts(out, filename) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int checkout_path_suffixed(git_buf *path, const char *suffix)
+{
+ size_t path_len;
+ int i = 0, error = 0;
+
+ if ((error = git_buf_putc(path, '~')) < 0 || (error = git_buf_puts(path, suffix)) < 0)
+ return -1;
+
+ path_len = git_buf_len(path);
+
+ while (git_path_exists(git_buf_cstr(path)) && i < INT_MAX) {
+ git_buf_truncate(path, path_len);
+
+ if ((error = git_buf_putc(path, '_')) < 0 ||
+ (error = git_buf_printf(path, "%d", i)) < 0)
+ return error;
+
+ i++;
+ }
+
+ if (i == INT_MAX) {
+ git_buf_truncate(path, path_len);
+
+ giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path->ptr);
+ return GIT_EEXISTS;
+ }
+
+ return 0;
+}
+
+static int checkout_write_entry(
+ checkout_data *data,
+ checkout_conflictdata *conflict,
+ const git_index_entry *side)
+{
+ const char *hint_path = NULL, *suffix;
+ git_buf *fullpath;
+ struct stat st;
+ int error;
+
+ assert (side == conflict->ours || side == conflict->theirs);
+
+ if (checkout_target_fullpath(&fullpath, data, side->path) < 0)
+ return -1;
+
+ if ((conflict->name_collision || conflict->directoryfile) &&
+ (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 &&
+ (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) {
+
+ if (side == conflict->ours)
+ suffix = data->opts.our_label ? data->opts.our_label :
+ "ours";
+ else
+ suffix = data->opts.their_label ? data->opts.their_label :
+ "theirs";
+
+ if (checkout_path_suffixed(fullpath, suffix) < 0)
+ return -1;
+
+ hint_path = side->path;
+ }
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
+ (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0)
+ return error;
+
+ return checkout_write_content(data,
+ &side->id, fullpath->ptr, hint_path, side->mode, &st);
+}
+
+static int checkout_write_entries(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ int error = 0;
+
+ if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ return error;
+}
+
+static int checkout_merge_path(
+ git_buf *out,
+ checkout_data *data,
+ checkout_conflictdata *conflict,
+ git_merge_file_result *result)
+{
+ const char *our_label_raw, *their_label_raw, *suffix;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0)
+ return error;
+
+ /* Most conflicts simply use the filename in the index */
+ if (!conflict->name_collision)
+ return 0;
+
+ /* Rename 2->1 conflicts need the branch name appended */
+ our_label_raw = data->opts.our_label ? data->opts.our_label : "ours";
+ their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs";
+ suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw;
+
+ if ((error = checkout_path_suffixed(out, suffix)) < 0)
+ return error;
+
+ return 0;
+}
+
+static int checkout_write_merge(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT,
+ path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT,
+ in_data = GIT_BUF_INIT, out_data = GIT_BUF_INIT;
+ git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ git_merge_file_result result = {0};
+ git_filebuf output = GIT_FILEBUF_INIT;
+ git_filter_list *fl = NULL;
+ git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
+ int error = 0;
+
+ if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)
+ opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3;
+
+ opts.ancestor_label = data->opts.ancestor_label ?
+ data->opts.ancestor_label : "ancestor";
+ opts.our_label = data->opts.our_label ?
+ data->opts.our_label : "ours";
+ opts.their_label = data->opts.their_label ?
+ data->opts.their_label : "theirs";
+
+ /* If all the paths are identical, decorate the diff3 file with the branch
+ * names. Otherwise, append branch_name:path.
+ */
+ if (conflict->ours && conflict->theirs &&
+ strcmp(conflict->ours->path, conflict->theirs->path) != 0) {
+
+ if ((error = conflict_entry_name(
+ &our_label, opts.our_label, conflict->ours->path)) < 0 ||
+ (error = conflict_entry_name(
+ &their_label, opts.their_label, conflict->theirs->path)) < 0)
+ goto done;
+
+ opts.our_label = git_buf_cstr(&our_label);
+ opts.their_label = git_buf_cstr(&their_label);
+ }
+
+ if ((error = git_merge_file_from_index(&result, data->repo,
+ conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0)
+ goto done;
+
+ if (result.path == NULL || result.mode == 0) {
+ giterr_set(GITERR_CHECKOUT, "Could not merge contents of file");
+ error = GIT_ECONFLICT;
+ goto done;
+ }
+
+ if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0)
+ goto done;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
+ (error = checkout_safe_for_update_only(data, git_buf_cstr(&path_workdir), result.mode)) <= 0)
+ goto done;
+
+ if (!data->opts.disable_filters) {
+ in_data.ptr = (char *)result.ptr;
+ in_data.size = result.len;
+
+ filter_opts.attr_session = &data->attr_session;
+ filter_opts.temp_buf = &data->tmp;
+
+ if ((error = git_filter_list__load_ext(
+ &fl, data->repo, NULL, git_buf_cstr(&path_workdir),
+ GIT_FILTER_TO_WORKTREE, &filter_opts)) < 0 ||
+ (error = git_filter_list_apply_to_data(&out_data, fl, &in_data)) < 0)
+ goto done;
+ } else {
+ out_data.ptr = (char *)result.ptr;
+ out_data.size = result.len;
+ }
+
+ if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 ||
+ (error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
+ (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 ||
+ (error = git_filebuf_commit(&output)) < 0)
+ goto done;
+
+done:
+ git_filter_list_free(fl);
+
+ git_buf_free(&out_data);
+ git_buf_free(&our_label);
+ git_buf_free(&their_label);
+
+ git_merge_file_result_free(&result);
+ git_buf_free(&path_workdir);
+ git_buf_free(&path_suffixed);
+
+ return error;
+}
+
+static int checkout_conflict_add(
+ checkout_data *data,
+ const git_index_entry *conflict)
+{
+ int error = git_index_remove(data->index, conflict->path, 0);
+
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ return error;
+
+ return git_index_add(data->index, conflict);
+}
+
+static int checkout_conflict_update_index(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ int error = 0;
+
+ if (conflict->ancestor)
+ error = checkout_conflict_add(data, conflict->ancestor);
+
+ if (!error && conflict->ours)
+ error = checkout_conflict_add(data, conflict->ours);
+
+ if (!error && conflict->theirs)
+ error = checkout_conflict_add(data, conflict->theirs);
+
+ return error;
+}
+
+static int checkout_create_conflicts(checkout_data *data)
+{
+ checkout_conflictdata *conflict;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(&data->update_conflicts, i, conflict) {
+
+ /* Both deleted: nothing to do */
+ if (conflict->ours == NULL && conflict->theirs == NULL)
+ error = 0;
+
+ else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
+ conflict->ours)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
+ conflict->theirs)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ /* Ignore the other side of name collisions. */
+ else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
+ !conflict->ours && conflict->name_collision)
+ error = 0;
+ else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
+ !conflict->theirs && conflict->name_collision)
+ error = 0;
+
+ /* For modify/delete, name collisions and d/f conflicts, write
+ * the file (potentially with the name mangled.
+ */
+ else if (conflict->ours != NULL && conflict->theirs == NULL)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ else if (conflict->ours == NULL && conflict->theirs != NULL)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ /* Add/add conflicts and rename 1->2 conflicts, write the
+ * ours/theirs sides (potentially name mangled).
+ */
+ else if (conflict->one_to_two)
+ error = checkout_write_entries(data, conflict);
+
+ /* If all sides are links, write the ours side */
+ else if (S_ISLNK(conflict->ours->mode) &&
+ S_ISLNK(conflict->theirs->mode))
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ /* Link/file conflicts, write the file side */
+ else if (S_ISLNK(conflict->ours->mode))
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+ else if (S_ISLNK(conflict->theirs->mode))
+ error = checkout_write_entry(data, conflict, conflict->ours);
+
+ /* If any side is a gitlink, do nothing. */
+ else if (conflict->submodule)
+ error = 0;
+
+ /* If any side is binary, write the ours side */
+ else if (conflict->binary)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+
+ else if (!error)
+ error = checkout_write_merge(data, conflict);
+
+ /* Update the index extensions (REUC and NAME) if we're checking
+ * out a different index. (Otherwise just leave them there.)
+ */
+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = checkout_conflict_update_index(data, conflict);
+
+ if (error)
+ break;
+
+ data->completed_steps++;
+ report_progress(data,
+ conflict->ours ? conflict->ours->path :
+ (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path));
+ }
+
+ return error;
+}
+
+static int checkout_remove_conflicts(checkout_data *data)
+{
+ const char *conflict;
+ size_t i;
+
+ git_vector_foreach(&data->remove_conflicts, i, conflict) {
+ if (git_index_conflict_remove(data->index, conflict) < 0)
+ return -1;
+
+ data->completed_steps++;
+ }
+
+ return 0;
+}
+
+static int checkout_extensions_update_index(checkout_data *data)
+{
+ const git_index_reuc_entry *reuc_entry;
+ const git_index_name_entry *name_entry;
+ size_t i;
+ int error = 0;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
+ if (data->update_reuc) {
+ git_vector_foreach(data->update_reuc, i, reuc_entry) {
+ if ((error = git_index_reuc_add(data->index, reuc_entry->path,
+ reuc_entry->mode[0], &reuc_entry->oid[0],
+ reuc_entry->mode[1], &reuc_entry->oid[1],
+ reuc_entry->mode[2], &reuc_entry->oid[2])) < 0)
+ goto done;
+ }
+ }
+
+ if (data->update_names) {
+ git_vector_foreach(data->update_names, i, name_entry) {
+ if ((error = git_index_name_add(data->index, name_entry->ancestor,
+ name_entry->ours, name_entry->theirs)) < 0)
+ goto done;
+ }
+ }
+
+done:
+ return error;
+}
+
+static void checkout_data_clear(checkout_data *data)
+{
+ if (data->opts_free_baseline) {
+ git_tree_free(data->opts.baseline);
+ data->opts.baseline = NULL;
+ }
+
+ git_vector_free(&data->removes);
+ git_pool_clear(&data->pool);
+
+ git_vector_free_deep(&data->remove_conflicts);
+ git_vector_free_deep(&data->update_conflicts);
+
+ git__free(data->pfx);
+ data->pfx = NULL;
+
+ git_strmap_free(data->mkdir_map);
+
+ git_buf_free(&data->target_path);
+ git_buf_free(&data->tmp);
+
+ git_index_free(data->index);
+ data->index = NULL;
+
+ git_strmap_free(data->mkdir_map);
+
+ git_attr_session__free(&data->attr_session);
+}
+
+static int checkout_data_init(
+ checkout_data *data,
+ git_iterator *target,
+ const git_checkout_options *proposed)
+{
+ int error = 0;
+ git_repository *repo = git_iterator_owner(target);
+
+ memset(data, 0, sizeof(*data));
+
+ if (!repo) {
+ giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
+ return -1;
+ }
+
+ if ((!proposed || !proposed->target_directory) &&
+ (error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
+ return error;
+
+ data->repo = repo;
+ data->target = target;
+
+ GITERR_CHECK_VERSION(
+ proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options");
+
+ if (!proposed)
+ GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION);
+ else
+ memmove(&data->opts, proposed, sizeof(git_checkout_options));
+
+ if (!data->opts.target_directory)
+ data->opts.target_directory = git_repository_workdir(repo);
+ else if (!git_path_isdir(data->opts.target_directory) &&
+ (error = checkout_mkdir(data,
+ data->opts.target_directory, NULL,
+ GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
+ goto cleanup;
+
+ /* refresh config and index content unless NO_REFRESH is given */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
+ git_config *cfg;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ goto cleanup;
+
+ /* Get the repository index and reload it (unless we're checking
+ * out the index; then it has the changes we're trying to check
+ * out and those should not be overwritten.)
+ */
+ if ((error = git_repository_index(&data->index, data->repo)) < 0)
+ goto cleanup;
+
+ if (data->index != git_iterator_index(target)) {
+ if ((error = git_index_read(data->index, true)) < 0)
+ goto cleanup;
+
+ /* cannot checkout if unresolved conflicts exist */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 &&
+ git_index_has_conflicts(data->index)) {
+ error = GIT_ECONFLICT;
+ giterr_set(GITERR_CHECKOUT,
+ "unresolved conflicts exist in the index");
+ goto cleanup;
+ }
+
+ /* clean conflict data in the current index */
+ git_index_name_clear(data->index);
+ git_index_reuc_clear(data->index);
+ }
+ }
+
+ /* if you are forcing, allow all safe updates, plus recreate missing */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE |
+ GIT_CHECKOUT_RECREATE_MISSING;
+
+ /* if the repository does not actually have an index file, then this
+ * is an initial checkout (perhaps from clone), so we allow safe updates
+ */
+ if (!data->index->on_disk &&
+ (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING;
+
+ data->strategy = data->opts.checkout_strategy;
+
+ /* opts->disable_filters is false by default */
+
+ if (!data->opts.dir_mode)
+ data->opts.dir_mode = GIT_DIR_MODE;
+
+ if (!data->opts.file_open_flags)
+ data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+
+ data->pfx = git_pathspec_prefix(&data->opts.paths);
+
+ if ((error = git_repository__cvar(
+ &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0)
+ goto cleanup;
+
+ if (!data->opts.baseline && !data->opts.baseline_index) {
+ data->opts_free_baseline = true;
+ error = 0;
+
+ /* if we don't have an index, this is an initial checkout and
+ * should be against an empty baseline
+ */
+ if (data->index->on_disk)
+ error = checkout_lookup_head_tree(&data->opts.baseline, repo);
+
+ if (error == GIT_EUNBORNBRANCH) {
+ error = 0;
+ giterr_clear();
+ }
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((data->opts.checkout_strategy &
+ (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) {
+ git_config_entry *conflict_style = NULL;
+ git_config *cfg = NULL;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 ||
+ (error = git_config_get_entry(&conflict_style, cfg, "merge.conflictstyle")) < 0 ||
+ error == GIT_ENOTFOUND)
+ ;
+ else if (error)
+ goto cleanup;
+ else if (strcmp(conflict_style->value, "merge") == 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE;
+ else if (strcmp(conflict_style->value, "diff3") == 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3;
+ else {
+ giterr_set(GITERR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'",
+ conflict_style->value);
+ error = -1;
+ git_config_entry_free(conflict_style);
+ goto cleanup;
+ }
+ git_config_entry_free(conflict_style);
+ }
+
+ git_pool_init(&data->pool, 1);
+
+ if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
+ (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 ||
+ (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 ||
+ (error = git_buf_puts(&data->target_path, data->opts.target_directory)) < 0 ||
+ (error = git_path_to_dir(&data->target_path)) < 0 ||
+ (error = git_strmap_alloc(&data->mkdir_map)) < 0)
+ goto cleanup;
+
+ data->target_len = git_buf_len(&data->target_path);
+
+ git_attr_session__init(&data->attr_session, data->repo);
+
+cleanup:
+ if (error < 0)
+ checkout_data_clear(data);
+
+ return error;
+}
+
+#define CHECKOUT_INDEX_DONT_WRITE_MASK \
+ (GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX)
+
+int git_checkout_iterator(
+ git_iterator *target,
+ git_index *index,
+ const git_checkout_options *opts)
+{
+ int error = 0;
+ git_iterator *baseline = NULL, *workdir = NULL;
+ git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT,
+ workdir_opts = GIT_ITERATOR_OPTIONS_INIT;
+ checkout_data data = {0};
+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+ uint32_t *actions = NULL;
+ size_t *counts = NULL;
+
+ /* initialize structures and options */
+ error = checkout_data_init(&data, target, opts);
+ if (error < 0)
+ return error;
+
+ diff_opts.flags =
+ GIT_DIFF_INCLUDE_UNMODIFIED |
+ GIT_DIFF_INCLUDE_UNREADABLE |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
+ GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_TYPECHANGE |
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
+ GIT_DIFF_SKIP_BINARY_CHECK |
+ GIT_DIFF_INCLUDE_CASECHANGE;
+ if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)
+ diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if (data.opts.paths.count > 0)
+ diff_opts.pathspec = data.opts.paths;
+
+ /* set up iterators */
+
+ workdir_opts.flags = git_iterator_ignore_case(target) ?
+ GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
+ workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND;
+ workdir_opts.start = data.pfx;
+ workdir_opts.end = data.pfx;
+
+ if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_workdir_ext(
+ &workdir, data.repo, data.opts.target_directory, index, NULL,
+ &workdir_opts)) < 0)
+ goto cleanup;
+
+ baseline_opts.flags = git_iterator_ignore_case(target) ?
+ GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
+ baseline_opts.start = data.pfx;
+ baseline_opts.end = data.pfx;
+
+ if (data.opts.baseline_index) {
+ if ((error = git_iterator_for_index(
+ &baseline, git_index_owner(data.opts.baseline_index),
+ data.opts.baseline_index, &baseline_opts)) < 0)
+ goto cleanup;
+ } else {
+ if ((error = git_iterator_for_tree(
+ &baseline, data.opts.baseline, &baseline_opts)) < 0)
+ goto cleanup;
+ }
+
+ /* Should not have case insensitivity mismatch */
+ assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));
+
+ /* Generate baseline-to-target diff which will include an entry for
+ * every possible update that might need to be made.
+ */
+ if ((error = git_diff__from_iterators(
+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)
+ goto cleanup;
+
+ /* Loop through diff (and working directory iterator) building a list of
+ * actions to be taken, plus look for conflicts and send notifications,
+ * then loop through conflicts.
+ */
+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0)
+ goto cleanup;
+
+ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
+ counts[CHECKOUT_ACTION__REMOVE_CONFLICT] +
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] +
+ counts[CHECKOUT_ACTION__UPDATE_CONFLICT];
+
+ report_progress(&data, NULL); /* establish 0 baseline */
+
+ /* To deal with some order dependencies, perform remaining checkout
+ * in three passes: removes, then update blobs, then update submodules.
+ */
+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
+ (error = checkout_remove_the_old(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__REMOVE_CONFLICT] > 0 &&
+ (error = checkout_remove_conflicts(&data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
+ (error = checkout_create_the_new(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
+ (error = checkout_create_submodules(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 &&
+ (error = checkout_create_conflicts(&data)) < 0)
+ goto cleanup;
+
+ if (data.index != git_iterator_index(target) &&
+ (error = checkout_extensions_update_index(&data)) < 0)
+ goto cleanup;
+
+ assert(data.completed_steps == data.total_steps);
+
+ if (data.opts.perfdata_cb)
+ data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload);
+
+cleanup:
+ if (!error && data.index != NULL &&
+ (data.strategy & CHECKOUT_INDEX_DONT_WRITE_MASK) == 0)
+ error = git_index_write(data.index);
+
+ git_diff_free(data.diff);
+ git_iterator_free(workdir);
+ git_iterator_free(baseline);
+ git__free(actions);
+ git__free(counts);
+ checkout_data_clear(&data);
+
+ return error;
+}
+
+int git_checkout_index(
+ git_repository *repo,
+ git_index *index,
+ const git_checkout_options *opts)
+{
+ int error, owned = 0;
+ git_iterator *index_i;
+
+ if (!index && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or index to checkout");
+ return -1;
+ }
+
+ if (index && repo &&
+ git_index_owner(index) &&
+ git_index_owner(index) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Index to checkout does not match repository");
+ return -1;
+ } else if(index && repo && !git_index_owner(index)) {
+ GIT_REFCOUNT_OWN(index, repo);
+ owned = 1;
+ }
+
+ if (!repo)
+ repo = git_index_owner(index);
+
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
+ GIT_REFCOUNT_INC(index);
+
+ if (!(error = git_iterator_for_index(&index_i, repo, index, NULL)))
+ error = git_checkout_iterator(index_i, index, opts);
+
+ if (owned)
+ GIT_REFCOUNT_OWN(index, NULL);
+
+ git_iterator_free(index_i);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_checkout_tree(
+ git_repository *repo,
+ const git_object *treeish,
+ const git_checkout_options *opts)
+{
+ int error;
+ git_index *index;
+ git_tree *tree = NULL;
+ git_iterator *tree_i = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ if (!treeish && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or tree to checkout");
+ return -1;
+ }
+ if (treeish && repo && git_object_owner(treeish) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Object to checkout does not match repository");
+ return -1;
+ }
+
+ if (!repo)
+ repo = git_object_owner(treeish);
+
+ if (treeish) {
+ if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
+ }
+ }
+ else {
+ if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) {
+ if (error != GIT_EUNBORNBRANCH)
+ giterr_set(
+ GITERR_CHECKOUT,
+ "HEAD could not be peeled to a tree and no treeish given");
+ return error;
+ }
+ }
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ return error;
+
+ if (opts && (opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) {
+ iter_opts.pathlist.count = opts->paths.count;
+ iter_opts.pathlist.strings = opts->paths.strings;
+ }
+
+ if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts)))
+ error = git_checkout_iterator(tree_i, index, opts);
+
+ git_iterator_free(tree_i);
+ git_index_free(index);
+ git_tree_free(tree);
+
+ return error;
+}
+
+int git_checkout_head(
+ git_repository *repo,
+ const git_checkout_options *opts)
+{
+ assert(repo);
+ return git_checkout_tree(repo, NULL, opts);
+}
+
+int git_checkout_init_options(git_checkout_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_checkout_h__
+#define INCLUDE_checkout_h__
+
+#include "git2/checkout.h"
+#include "iterator.h"
+
+#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
+
+/**
+ * Update the working directory to match the target iterator. The
+ * expected baseline value can be passed in via the checkout options
+ * or else will default to the HEAD commit.
+ */
+extern int git_checkout_iterator(
+ git_iterator *target,
+ git_index *index,
+ const git_checkout_options *opts);
+
+#endif
--- /dev/null
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include "merge.h"
+#include "vector.h"
+#include "index.h"
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "git2/cherrypick.h"
+#include "git2/commit.h"
+#include "git2/sys/commit.h"
+
+#define GIT_CHERRYPICK_FILE_MODE 0666
+
+static int write_cherrypick_head(
+ git_repository *repo,
+ const char *commit_oidstr)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const char *commit_msg)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
+ (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int cherrypick_normalize_opts(
+ git_repository *repo,
+ git_cherrypick_options *opts,
+ const git_cherrypick_options *given,
+ const char *their_label)
+{
+ int error = 0;
+ unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
+ GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ GIT_UNUSED(repo);
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_cherrypick_options));
+ else {
+ git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT;
+ memcpy(opts, &default_opts, sizeof(git_cherrypick_options));
+ }
+
+ if (!opts->checkout_opts.checkout_strategy)
+ opts->checkout_opts.checkout_strategy = default_checkout_strategy;
+
+ if (!opts->checkout_opts.our_label)
+ opts->checkout_opts.our_label = "HEAD";
+
+ if (!opts->checkout_opts.their_label)
+ opts->checkout_opts.their_label = their_label;
+
+ return error;
+}
+
+static int cherrypick_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int cherrypick_seterr(git_commit *commit, const char *fmt)
+{
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ giterr_set(GITERR_CHERRYPICK, fmt,
+ git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
+
+ return -1;
+}
+
+int git_cherrypick_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *cherrypick_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_opts)
+{
+ git_commit *parent_commit = NULL;
+ git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL;
+ int parent = 0, error = 0;
+
+ assert(out && repo && cherrypick_commit && our_commit);
+
+ if (git_commit_parentcount(cherrypick_commit) > 1) {
+ if (!mainline)
+ return cherrypick_seterr(cherrypick_commit,
+ "Mainline branch is not specified but %s is a merge commit");
+
+ parent = mainline;
+ } else {
+ if (mainline)
+ return cherrypick_seterr(cherrypick_commit,
+ "Mainline branch specified but %s is not a merge commit");
+
+ parent = git_commit_parentcount(cherrypick_commit);
+ }
+
+ if (parent &&
+ ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0))
+ goto done;
+
+ if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 ||
+ (error = git_commit_tree(&our_tree, our_commit)) < 0)
+ goto done;
+
+ error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts);
+
+done:
+ git_tree_free(parent_tree);
+ git_tree_free(our_tree);
+ git_tree_free(cherrypick_tree);
+ git_commit_free(parent_commit);
+
+ return error;
+}
+
+int git_cherrypick(
+ git_repository *repo,
+ git_commit *commit,
+ const git_cherrypick_options *given_opts)
+{
+ git_cherrypick_options opts;
+ git_reference *our_ref = NULL;
+ git_commit *our_commit = NULL;
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+ const char *commit_msg, *commit_summary;
+ git_buf their_label = GIT_BUF_INIT;
+ git_index *index = NULL;
+ git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
+ int error = 0;
+
+ assert(repo && commit);
+
+ GITERR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options");
+
+ if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0)
+ return error;
+
+ if ((commit_msg = git_commit_message(commit)) == NULL ||
+ (commit_summary = git_commit_summary(commit)) == NULL) {
+ error = -1;
+ goto on_error;
+ }
+
+ git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit));
+
+ if ((error = write_merge_msg(repo, commit_msg)) < 0 ||
+ (error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 ||
+ (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
+ (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 ||
+ (error = write_cherrypick_head(repo, commit_oidstr)) < 0 ||
+ (error = git_repository_head(&our_ref, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
+ (error = git_merge__check_result(repo, index)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 ||
+ (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 ||
+ (error = git_indexwriter_commit(&indexwriter)) < 0)
+ goto on_error;
+
+ goto done;
+
+on_error:
+ cherrypick_state_cleanup(repo);
+
+done:
+ git_indexwriter_cleanup(&indexwriter);
+ git_index_free(index);
+ git_commit_free(our_commit);
+ git_reference_free(our_ref);
+ git_buf_free(&their_label);
+
+ return error;
+}
+
+int git_cherrypick_init_options(
+ git_cherrypick_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "git2/clone.h"
+#include "git2/remote.h"
+#include "git2/revparse.h"
+#include "git2/branch.h"
+#include "git2/config.h"
+#include "git2/checkout.h"
+#include "git2/commit.h"
+#include "git2/tree.h"
+
+#include "common.h"
+#include "remote.h"
+#include "fileops.h"
+#include "refs.h"
+#include "path.h"
+#include "repository.h"
+#include "odb.h"
+
+static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link);
+
+static int create_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *name,
+ const char *log_message)
+{
+ git_commit *head_obj = NULL;
+ git_reference *branch_ref = NULL;
+ git_buf refname = GIT_BUF_INIT;
+ int error;
+
+ /* Find the target commit */
+ if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)
+ return error;
+
+ /* Create the new branch */
+ if ((error = git_buf_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0)
+ return error;
+
+ error = git_reference_create(&branch_ref, repo, git_buf_cstr(&refname), target, 0, log_message);
+ git_buf_free(&refname);
+ git_commit_free(head_obj);
+
+ if (!error)
+ *branch = branch_ref;
+ else
+ git_reference_free(branch_ref);
+
+ return error;
+}
+
+static int setup_tracking_config(
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_name,
+ const char *merge_target)
+{
+ git_config *cfg;
+ git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_repository_config__weakptr(&cfg, repo) < 0)
+ return -1;
+
+ if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&remote_key);
+ git_buf_free(&merge_key);
+ return error;
+}
+
+static int create_tracking_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *branch_name,
+ const char *log_message)
+{
+ int error;
+
+ if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0)
+ return error;
+
+ return setup_tracking_config(
+ repo,
+ branch_name,
+ GIT_REMOTE_ORIGIN,
+ git_reference_name(*branch));
+}
+
+static int update_head_to_new_branch(
+ git_repository *repo,
+ const git_oid *target,
+ const char *name,
+ const char *reflog_message)
+{
+ git_reference *tracking_branch = NULL;
+ int error;
+
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ name += strlen(GIT_REFS_HEADS_DIR);
+
+ error = create_tracking_branch(&tracking_branch, repo, target, name,
+ reflog_message);
+
+ if (!error)
+ error = git_repository_set_head(
+ repo, git_reference_name(tracking_branch));
+
+ git_reference_free(tracking_branch);
+
+ /* if it already existed, then the user's refspec created it for us, ignore it' */
+ if (error == GIT_EEXISTS)
+ error = 0;
+
+ return error;
+}
+
+static int update_head_to_remote(
+ git_repository *repo,
+ git_remote *remote,
+ const char *reflog_message)
+{
+ int error = 0;
+ size_t refs_len;
+ git_refspec *refspec;
+ const git_remote_head *remote_head, **refs;
+ const git_oid *remote_head_id;
+ git_buf remote_master_name = GIT_BUF_INIT;
+ git_buf branch = GIT_BUF_INIT;
+
+ if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0)
+ return error;
+
+ /* We cloned an empty repository or one with an unborn HEAD */
+ if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE))
+ return setup_tracking_config(
+ repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
+
+ /* We know we have HEAD, let's see where it points */
+ remote_head = refs[0];
+ assert(remote_head);
+
+ remote_head_id = &remote_head->oid;
+
+ error = git_remote_default_branch(&branch, remote);
+ if (error == GIT_ENOTFOUND) {
+ error = git_repository_set_head_detached(
+ repo, remote_head_id);
+ goto cleanup;
+ }
+
+ refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch));
+
+ if (refspec == NULL) {
+ giterr_set(GITERR_NET, "the remote's default branch does not fit the refspec configuration");
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ /* Determine the remote tracking reference name from the local master */
+ if ((error = git_refspec_transform(
+ &remote_master_name,
+ refspec,
+ git_buf_cstr(&branch))) < 0)
+ goto cleanup;
+
+ error = update_head_to_new_branch(
+ repo,
+ remote_head_id,
+ git_buf_cstr(&branch),
+ reflog_message);
+
+cleanup:
+ git_buf_free(&remote_master_name);
+ git_buf_free(&branch);
+
+ return error;
+}
+
+static int update_head_to_branch(
+ git_repository *repo,
+ const char *remote_name,
+ const char *branch,
+ const char *reflog_message)
+{
+ int retcode;
+ git_buf remote_branch_name = GIT_BUF_INIT;
+ git_reference* remote_ref = NULL;
+
+ assert(remote_name && branch);
+
+ if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
+ remote_name, branch)) < 0 )
+ goto cleanup;
+
+ if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
+ goto cleanup;
+
+ retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch,
+ reflog_message);
+
+cleanup:
+ git_reference_free(remote_ref);
+ git_buf_free(&remote_branch_name);
+ return retcode;
+}
+
+static int default_repository_create(git_repository **out, const char *path, int bare, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ return git_repository_init(out, path, bare);
+}
+
+static int default_remote_create(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url,
+ void *payload)
+{
+ GIT_UNUSED(payload);
+
+ return git_remote_create(out, repo, name, url);
+}
+
+/*
+ * submodules?
+ */
+
+static int create_and_configure_origin(
+ git_remote **out,
+ git_repository *repo,
+ const char *url,
+ const git_clone_options *options)
+{
+ int error;
+ git_remote *origin = NULL;
+ char buf[GIT_PATH_MAX];
+ git_remote_create_cb remote_create = options->remote_cb;
+ void *payload = options->remote_cb_payload;
+
+ /* If the path exists and is a dir, the url should be the absolute path */
+ if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) {
+ if (p_realpath(url, buf) == NULL)
+ return -1;
+
+ url = buf;
+ }
+
+ if (!remote_create) {
+ remote_create = default_remote_create;
+ payload = NULL;
+ }
+
+ if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0)
+ goto on_error;
+
+ *out = origin;
+ return 0;
+
+on_error:
+ git_remote_free(origin);
+ return error;
+}
+
+static bool should_checkout(
+ git_repository *repo,
+ bool is_bare,
+ const git_checkout_options *opts)
+{
+ if (is_bare)
+ return false;
+
+ if (!opts)
+ return false;
+
+ if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
+ return false;
+
+ return !git_repository_head_unborn(repo);
+}
+
+static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message)
+{
+ int error;
+
+ if (branch)
+ error = update_head_to_branch(repo, git_remote_name(remote), branch,
+ reflog_message);
+ /* Point HEAD to the same ref as the remote's head */
+ else
+ error = update_head_to_remote(repo, remote, reflog_message);
+
+ if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
+ error = git_checkout_head(repo, co_opts);
+
+ return error;
+}
+
+static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch)
+{
+ int error;
+ git_buf reflog_message = GIT_BUF_INIT;
+ git_fetch_options fetch_opts;
+ git_remote *remote;
+
+ assert(repo && _remote);
+
+ if (!git_repository_is_empty(repo)) {
+ giterr_set(GITERR_INVALID, "the repository is not empty");
+ return -1;
+ }
+
+ if ((error = git_remote_dup(&remote, _remote)) < 0)
+ return error;
+
+ memcpy(&fetch_opts, opts, sizeof(git_fetch_options));
+ fetch_opts.update_fetchhead = 0;
+ fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
+ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
+
+ if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_buf_cstr(&reflog_message))) != 0)
+ goto cleanup;
+
+ error = checkout_branch(repo, remote, co_opts, branch, git_buf_cstr(&reflog_message));
+
+cleanup:
+ git_remote_free(remote);
+ git_buf_free(&reflog_message);
+
+ return error;
+}
+
+int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local)
+{
+ git_buf fromurl = GIT_BUF_INIT;
+ const char *path = url_or_path;
+ bool is_url, is_local;
+
+ if (local == GIT_CLONE_NO_LOCAL)
+ return 0;
+
+ if ((is_url = git_path_is_local_file_url(url_or_path)) != 0) {
+ if (git_path_fromurl(&fromurl, url_or_path) < 0) {
+ is_local = -1;
+ goto done;
+ }
+
+ path = fromurl.ptr;
+ }
+
+ is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) &&
+ git_path_isdir(path);
+
+done:
+ git_buf_free(&fromurl);
+ return is_local;
+}
+
+int git_clone(
+ git_repository **out,
+ const char *url,
+ const char *local_path,
+ const git_clone_options *_options)
+{
+ int error = 0;
+ git_repository *repo = NULL;
+ git_remote *origin;
+ git_clone_options options = GIT_CLONE_OPTIONS_INIT;
+ uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
+ git_repository_create_cb repository_cb;
+
+ assert(out && url && local_path);
+
+ if (_options)
+ memcpy(&options, _options, sizeof(git_clone_options));
+
+ GITERR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
+
+ /* Only clone to a new directory or an empty directory */
+ if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
+ giterr_set(GITERR_INVALID,
+ "'%s' exists and is not an empty directory", local_path);
+ return GIT_EEXISTS;
+ }
+
+ /* Only remove the root directory on failure if we create it */
+ if (git_path_exists(local_path))
+ rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
+
+ if (options.repository_cb)
+ repository_cb = options.repository_cb;
+ else
+ repository_cb = default_repository_create;
+
+ if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0)
+ return error;
+
+ if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
+ int clone_local = git_clone__should_clone_local(url, options.local);
+ int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
+
+ if (clone_local == 1)
+ error = clone_local_into(
+ repo, origin, &options.fetch_opts, &options.checkout_opts,
+ options.checkout_branch, link);
+ else if (clone_local == 0)
+ error = clone_into(
+ repo, origin, &options.fetch_opts, &options.checkout_opts,
+ options.checkout_branch);
+ else
+ error = -1;
+
+ git_remote_free(origin);
+ }
+
+ if (error != 0) {
+ git_error_state last_error = {0};
+ giterr_state_capture(&last_error, error);
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
+
+ giterr_state_restore(&last_error);
+ }
+
+ *out = repo;
+ return error;
+}
+
+int git_clone_init_options(git_clone_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT);
+ return 0;
+}
+
+static bool can_link(const char *src, const char *dst, int link)
+{
+#ifdef GIT_WIN32
+ GIT_UNUSED(src);
+ GIT_UNUSED(dst);
+ GIT_UNUSED(link);
+ return false;
+#else
+
+ struct stat st_src, st_dst;
+
+ if (!link)
+ return false;
+
+ if (p_stat(src, &st_src) < 0)
+ return false;
+
+ if (p_stat(dst, &st_dst) < 0)
+ return false;
+
+ return st_src.st_dev == st_dst.st_dev;
+#endif
+}
+
+static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link)
+{
+ int error, flags;
+ git_repository *src;
+ git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT;
+ git_buf reflog_message = GIT_BUF_INIT;
+
+ assert(repo && remote);
+
+ if (!git_repository_is_empty(repo)) {
+ giterr_set(GITERR_INVALID, "the repository is not empty");
+ return -1;
+ }
+
+ /*
+ * Let's figure out what path we should use for the source
+ * repo, if it's not rooted, the path should be relative to
+ * the repository's worktree/gitdir.
+ */
+ if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0)
+ return error;
+
+ /* Copy .git/objects/ from the source to the target */
+ if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) {
+ git_buf_free(&src_path);
+ return error;
+ }
+
+ git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR);
+ git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR);
+ if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ flags = 0;
+ if (can_link(git_repository_path(src), git_repository_path(repo), link))
+ flags |= GIT_CPDIR_LINK_FILES;
+
+ error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
+ flags, GIT_OBJECT_DIR_MODE);
+
+ /*
+ * can_link() doesn't catch all variations, so if we hit an
+ * error and did want to link, let's try again without trying
+ * to link.
+ */
+ if (error < 0 && link) {
+ flags &= ~GIT_CPDIR_LINK_FILES;
+ error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
+ flags, GIT_OBJECT_DIR_MODE);
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
+
+ if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_buf_cstr(&reflog_message))) != 0)
+ goto cleanup;
+
+ error = checkout_branch(repo, remote, co_opts, branch, git_buf_cstr(&reflog_message));
+
+cleanup:
+ git_buf_free(&reflog_message);
+ git_buf_free(&src_path);
+ git_buf_free(&src_odb);
+ git_buf_free(&dst_odb);
+ git_repository_free(src);
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_clone_h__
+#define INCLUDE_clone_h__
+
+extern int git_clone__should_clone_local(const char *url, git_clone_local_t local);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/common.h"
+#include "git2/object.h"
+#include "git2/repository.h"
+#include "git2/signature.h"
+#include "git2/sys/commit.h"
+
+#include "common.h"
+#include "odb.h"
+#include "commit.h"
+#include "signature.h"
+#include "message.h"
+#include "refs.h"
+#include "object.h"
+#include "oidarray.h"
+
+void git_commit__free(void *_commit)
+{
+ git_commit *commit = _commit;
+
+ git_array_clear(commit->parent_ids);
+
+ git_signature_free(commit->author);
+ git_signature_free(commit->committer);
+
+ git__free(commit->raw_header);
+ git__free(commit->raw_message);
+ git__free(commit->message_encoding);
+ git__free(commit->summary);
+ git__free(commit->body);
+
+ git__free(commit);
+}
+
+static int git_commit__create_buffer_internal(
+ git_buf *out,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_array_oid_t *parents)
+{
+ size_t i = 0;
+ const git_oid *parent;
+
+ assert(out && tree);
+
+ git_oid__writebuf(out, "tree ", tree);
+
+ for (i = 0; i < git_array_size(*parents); i++) {
+ parent = git_array_get(*parents, i);
+ git_oid__writebuf(out, "parent ", parent);
+ }
+
+ git_signature__writebuf(out, "author ", author);
+ git_signature__writebuf(out, "committer ", committer);
+
+ if (message_encoding != NULL)
+ git_buf_printf(out, "encoding %s\n", message_encoding);
+
+ git_buf_putc(out, '\n');
+
+ if (git_buf_puts(out, message) < 0)
+ goto on_error;
+
+ return 0;
+
+on_error:
+ git_buf_free(out);
+ return -1;
+}
+
+static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree,
+ git_commit_parent_callback parent_cb, void *parent_payload,
+ const git_oid *current_id, bool validate)
+{
+ size_t i;
+ int error;
+ git_oid *parent_cpy;
+ const git_oid *parent;
+
+ if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE))
+ return -1;
+
+ i = 0;
+ while ((parent = parent_cb(i, parent_payload)) != NULL) {
+ if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) {
+ error = -1;
+ goto on_error;
+ }
+
+ parent_cpy = git_array_alloc(*parents);
+ GITERR_CHECK_ALLOC(parent_cpy);
+
+ git_oid_cpy(parent_cpy, parent);
+ i++;
+ }
+
+ if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) {
+ giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent");
+ error = GIT_EMODIFIED;
+ goto on_error;
+ }
+
+ return 0;
+
+on_error:
+ git_array_clear(*parents);
+ return error;
+}
+
+static int git_commit__create_internal(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_commit_parent_callback parent_cb,
+ void *parent_payload,
+ bool validate)
+{
+ int error;
+ git_odb *odb;
+ git_reference *ref = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ const git_oid *current_id = NULL;
+ git_array_oid_t parents = GIT_ARRAY_INIT;
+
+ if (update_ref) {
+ error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ }
+ giterr_clear();
+
+ if (ref)
+ current_id = git_reference_target(ref);
+
+ if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0)
+ goto cleanup;
+
+ error = git_commit__create_buffer_internal(&buf, author, committer,
+ message_encoding, message, tree,
+ &parents);
+
+ if (error < 0)
+ goto cleanup;
+
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ goto cleanup;
+
+ if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0)
+ goto cleanup;
+
+
+ if (update_ref != NULL) {
+ error = git_reference__update_for_commit(
+ repo, ref, update_ref, id, "commit");
+ goto cleanup;
+ }
+
+cleanup:
+ git_array_clear(parents);
+ git_reference_free(ref);
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_commit_create_from_callback(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_commit_parent_callback parent_cb,
+ void *parent_payload)
+{
+ return git_commit__create_internal(
+ id, repo, update_ref, author, committer, message_encoding, message,
+ tree, parent_cb, parent_payload, true);
+}
+
+typedef struct {
+ size_t total;
+ va_list args;
+} commit_parent_varargs;
+
+static const git_oid *commit_parent_from_varargs(size_t curr, void *payload)
+{
+ commit_parent_varargs *data = payload;
+ const git_commit *commit;
+ if (curr >= data->total)
+ return NULL;
+ commit = va_arg(data->args, const git_commit *);
+ return commit ? git_commit_id(commit) : NULL;
+}
+
+int git_commit_create_v(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ ...)
+{
+ int error = 0;
+ commit_parent_varargs data;
+
+ assert(tree && git_tree_owner(tree) == repo);
+
+ data.total = parent_count;
+ va_start(data.args, parent_count);
+
+ error = git_commit__create_internal(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, git_tree_id(tree),
+ commit_parent_from_varargs, &data, false);
+
+ va_end(data.args);
+ return error;
+}
+
+typedef struct {
+ size_t total;
+ const git_oid **parents;
+} commit_parent_oids;
+
+static const git_oid *commit_parent_from_ids(size_t curr, void *payload)
+{
+ commit_parent_oids *data = payload;
+ return (curr < data->total) ? data->parents[curr] : NULL;
+}
+
+int git_commit_create_from_ids(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ size_t parent_count,
+ const git_oid *parents[])
+{
+ commit_parent_oids data = { parent_count, parents };
+
+ return git_commit__create_internal(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, tree,
+ commit_parent_from_ids, &data, true);
+}
+
+typedef struct {
+ size_t total;
+ const git_commit **parents;
+ git_repository *repo;
+} commit_parent_data;
+
+static const git_oid *commit_parent_from_array(size_t curr, void *payload)
+{
+ commit_parent_data *data = payload;
+ const git_commit *commit;
+ if (curr >= data->total)
+ return NULL;
+ commit = data->parents[curr];
+ if (git_commit_owner(commit) != data->repo)
+ return NULL;
+ return git_commit_id(commit);
+}
+
+int git_commit_create(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[])
+{
+ commit_parent_data data = { parent_count, parents, repo };
+
+ assert(tree && git_tree_owner(tree) == repo);
+
+ return git_commit__create_internal(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, git_tree_id(tree),
+ commit_parent_from_array, &data, false);
+}
+
+static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
+{
+ const git_commit *commit_to_amend = payload;
+ if (curr >= git_array_size(commit_to_amend->parent_ids))
+ return NULL;
+ return git_array_get(commit_to_amend->parent_ids, curr);
+}
+
+int git_commit_amend(
+ git_oid *id,
+ const git_commit *commit_to_amend,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree)
+{
+ git_repository *repo;
+ git_oid tree_id;
+ git_reference *ref;
+ int error;
+
+ assert(id && commit_to_amend);
+
+ repo = git_commit_owner(commit_to_amend);
+
+ if (!author)
+ author = git_commit_author(commit_to_amend);
+ if (!committer)
+ committer = git_commit_committer(commit_to_amend);
+ if (!message_encoding)
+ message_encoding = git_commit_message_encoding(commit_to_amend);
+ if (!message)
+ message = git_commit_message(commit_to_amend);
+
+ if (!tree) {
+ git_tree *old_tree;
+ GITERR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) );
+ git_oid_cpy(&tree_id, git_tree_id(old_tree));
+ git_tree_free(old_tree);
+ } else {
+ assert(git_tree_owner(tree) == repo);
+ git_oid_cpy(&tree_id, git_tree_id(tree));
+ }
+
+ if (update_ref) {
+ if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
+ return error;
+
+ if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
+ git_reference_free(ref);
+ giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch");
+ return -1;
+ }
+ }
+
+ error = git_commit__create_internal(
+ id, repo, NULL, author, committer, message_encoding, message,
+ &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
+
+ if (!error && update_ref) {
+ error = git_reference__update_for_commit(
+ repo, ref, NULL, id, "commit");
+ git_reference_free(ref);
+ }
+
+ return error;
+}
+
+int git_commit__parse(void *_commit, git_odb_object *odb_obj)
+{
+ git_commit *commit = _commit;
+ const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
+ const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
+ git_oid parent_id;
+ size_t header_len;
+ git_signature dummy_sig;
+
+ buffer = buffer_start;
+
+ /* Allocate for one, which will allow not to realloc 90% of the time */
+ git_array_init_to_size(commit->parent_ids, 1);
+ GITERR_CHECK_ARRAY(commit->parent_ids);
+
+ /* The tree is always the first field */
+ if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
+ goto bad_buffer;
+
+ /*
+ * TODO: commit grafts!
+ */
+
+ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
+ git_oid *new_id = git_array_alloc(commit->parent_ids);
+ GITERR_CHECK_ALLOC(new_id);
+
+ git_oid_cpy(new_id, &parent_id);
+ }
+
+ commit->author = git__malloc(sizeof(git_signature));
+ GITERR_CHECK_ALLOC(commit->author);
+
+ if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
+ return -1;
+
+ /* Some tools create multiple author fields, ignore the extra ones */
+ while ((size_t)(buffer_end - buffer) >= strlen("author ") && !git__prefixcmp(buffer, "author ")) {
+ if (git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n') < 0)
+ return -1;
+
+ git__free(dummy_sig.name);
+ git__free(dummy_sig.email);
+ }
+
+ /* Always parse the committer; we need the commit time */
+ commit->committer = git__malloc(sizeof(git_signature));
+ GITERR_CHECK_ALLOC(commit->committer);
+
+ if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
+ return -1;
+
+ /* Parse add'l header entries */
+ while (buffer < buffer_end) {
+ const char *eoln = buffer;
+ if (buffer[-1] == '\n' && buffer[0] == '\n')
+ break;
+
+ while (eoln < buffer_end && *eoln != '\n')
+ ++eoln;
+
+ if (git__prefixcmp(buffer, "encoding ") == 0) {
+ buffer += strlen("encoding ");
+
+ commit->message_encoding = git__strndup(buffer, eoln - buffer);
+ GITERR_CHECK_ALLOC(commit->message_encoding);
+ }
+
+ if (eoln < buffer_end && *eoln == '\n')
+ ++eoln;
+ buffer = eoln;
+ }
+
+ header_len = buffer - buffer_start;
+ commit->raw_header = git__strndup(buffer_start, header_len);
+ GITERR_CHECK_ALLOC(commit->raw_header);
+
+ /* point "buffer" to data after header, +1 for the final LF */
+ buffer = buffer_start + header_len + 1;
+
+ /* extract commit message */
+ if (buffer <= buffer_end)
+ commit->raw_message = git__strndup(buffer, buffer_end - buffer);
+ else
+ commit->raw_message = git__strdup("");
+ GITERR_CHECK_ALLOC(commit->raw_message);
+
+ return 0;
+
+bad_buffer:
+ giterr_set(GITERR_OBJECT, "Failed to parse bad commit object");
+ return -1;
+}
+
+#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
+ _rvalue git_commit_##_name(const git_commit *commit) \
+ {\
+ assert(commit); \
+ return _return; \
+ }
+
+GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
+GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
+GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message)
+GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
+GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
+GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
+GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
+GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
+GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id)
+
+const char *git_commit_message(const git_commit *commit)
+{
+ const char *message;
+
+ assert(commit);
+
+ message = commit->raw_message;
+
+ /* trim leading newlines from raw message */
+ while (*message && *message == '\n')
+ ++message;
+
+ return message;
+}
+
+const char *git_commit_summary(git_commit *commit)
+{
+ git_buf summary = GIT_BUF_INIT;
+ const char *msg, *space;
+ bool space_contains_newline = false;
+
+ assert(commit);
+
+ if (!commit->summary) {
+ for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) {
+ char next_character = msg[0];
+ /* stop processing at the end of the first paragraph */
+ if (next_character == '\n' && (!msg[1] || msg[1] == '\n'))
+ break;
+ /* record the beginning of contiguous whitespace runs */
+ else if (git__isspace(next_character)) {
+ if(space == NULL) {
+ space = msg;
+ space_contains_newline = false;
+ }
+ space_contains_newline |= next_character == '\n';
+ }
+ /* the next character is non-space */
+ else {
+ /* process any recorded whitespace */
+ if (space) {
+ if(space_contains_newline)
+ git_buf_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */
+ else
+ git_buf_put(&summary, space, (msg - space)); /* otherwise copy it */
+ space = NULL;
+ }
+ /* copy the next character */
+ git_buf_putc(&summary, next_character);
+ }
+ }
+
+ commit->summary = git_buf_detach(&summary);
+ if (!commit->summary)
+ commit->summary = git__strdup("");
+ }
+
+ return commit->summary;
+}
+
+const char *git_commit_body(git_commit *commit)
+{
+ const char *msg, *end;
+
+ assert(commit);
+
+ if (!commit->body) {
+ /* search for end of summary */
+ for (msg = git_commit_message(commit); *msg; ++msg)
+ if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n'))
+ break;
+
+ /* trim leading and trailing whitespace */
+ for (; *msg; ++msg)
+ if (!git__isspace(*msg))
+ break;
+ for (end = msg + strlen(msg) - 1; msg <= end; --end)
+ if (!git__isspace(*end))
+ break;
+
+ if (*msg)
+ commit->body = git__strndup(msg, end - msg + 1);
+ }
+
+ return commit->body;
+}
+
+int git_commit_tree(git_tree **tree_out, const git_commit *commit)
+{
+ assert(commit);
+ return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
+}
+
+const git_oid *git_commit_parent_id(
+ const git_commit *commit, unsigned int n)
+{
+ assert(commit);
+
+ return git_array_get(commit->parent_ids, n);
+}
+
+int git_commit_parent(
+ git_commit **parent, const git_commit *commit, unsigned int n)
+{
+ const git_oid *parent_id;
+ assert(commit);
+
+ parent_id = git_commit_parent_id(commit, n);
+ if (parent_id == NULL) {
+ giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
+ return GIT_ENOTFOUND;
+ }
+
+ return git_commit_lookup(parent, commit->object.repo, parent_id);
+}
+
+int git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n)
+{
+ git_commit *current, *parent = NULL;
+ int error;
+
+ assert(ancestor && commit);
+
+ if (git_commit_dup(¤t, (git_commit *)commit) < 0)
+ return -1;
+
+ if (n == 0) {
+ *ancestor = current;
+ return 0;
+ }
+
+ while (n--) {
+ error = git_commit_parent(&parent, current, 0);
+
+ git_commit_free(current);
+
+ if (error < 0)
+ return error;
+
+ current = parent;
+ }
+
+ *ancestor = parent;
+ return 0;
+}
+
+int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field)
+{
+ const char *eol, *buf = commit->raw_header;
+
+ git_buf_sanitize(out);
+
+ while ((eol = strchr(buf, '\n'))) {
+ /* We can skip continuations here */
+ if (buf[0] == ' ') {
+ buf = eol + 1;
+ continue;
+ }
+
+ /* Skip until we find the field we're after */
+ if (git__prefixcmp(buf, field)) {
+ buf = eol + 1;
+ continue;
+ }
+
+ buf += strlen(field);
+ /* Check that we're not matching a prefix but the field itself */
+ if (buf[0] != ' ') {
+ buf = eol + 1;
+ continue;
+ }
+
+ buf++; /* skip the SP */
+
+ git_buf_put(out, buf, eol - buf);
+ if (git_buf_oom(out))
+ goto oom;
+
+ /* If the next line starts with SP, it's multi-line, we must continue */
+ while (eol[1] == ' ') {
+ git_buf_putc(out, '\n');
+ buf = eol + 2;
+ eol = strchr(buf, '\n');
+ if (!eol)
+ goto malformed;
+
+ git_buf_put(out, buf, eol - buf);
+ }
+
+ if (git_buf_oom(out))
+ goto oom;
+
+ return 0;
+ }
+
+ giterr_set(GITERR_OBJECT, "no such field '%s'", field);
+ return GIT_ENOTFOUND;
+
+malformed:
+ giterr_set(GITERR_OBJECT, "malformed header");
+ return -1;
+oom:
+ giterr_set_oom();
+ return -1;
+}
+
+int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field)
+{
+ git_odb_object *obj;
+ git_odb *odb;
+ const char *buf;
+ const char *h, *eol;
+ int error;
+
+ git_buf_sanitize(signature);
+ git_buf_sanitize(signed_data);
+
+ if (!field)
+ field = "gpgsig";
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ return error;
+
+ if ((error = git_odb_read(&obj, odb, commit_id)) < 0)
+ return error;
+
+ if (obj->cached.type != GIT_OBJ_COMMIT) {
+ giterr_set(GITERR_INVALID, "the requested type does not match the type in ODB");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ buf = git_odb_object_data(obj);
+
+ while ((h = strchr(buf, '\n')) && h[1] != '\0') {
+ h++;
+ if (git__prefixcmp(buf, field)) {
+ if (git_buf_put(signed_data, buf, h - buf) < 0)
+ return -1;
+
+ buf = h;
+ continue;
+ }
+
+ h = buf;
+ h += strlen(field);
+ eol = strchr(h, '\n');
+ if (h[0] != ' ') {
+ buf = h;
+ continue;
+ }
+ if (!eol)
+ goto malformed;
+
+ h++; /* skip the SP */
+
+ git_buf_put(signature, h, eol - h);
+ if (git_buf_oom(signature))
+ goto oom;
+
+ /* If the next line starts with SP, it's multi-line, we must continue */
+ while (eol[1] == ' ') {
+ git_buf_putc(signature, '\n');
+ h = eol + 2;
+ eol = strchr(h, '\n');
+ if (!eol)
+ goto malformed;
+
+ git_buf_put(signature, h, eol - h);
+ }
+
+ if (git_buf_oom(signature))
+ goto oom;
+
+ git_odb_object_free(obj);
+ return git_buf_puts(signed_data, eol+1);
+ }
+
+ giterr_set(GITERR_OBJECT, "this commit is not signed");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+
+malformed:
+ giterr_set(GITERR_OBJECT, "malformed header");
+ error = -1;
+ goto cleanup;
+oom:
+ giterr_set_oom();
+ error = -1;
+ goto cleanup;
+
+cleanup:
+ git_odb_object_free(obj);
+ git_buf_clear(signature);
+ git_buf_clear(signed_data);
+ return error;
+}
+
+int git_commit_create_buffer(git_buf *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[])
+{
+ int error;
+ commit_parent_data data = { parent_count, parents, repo };
+ git_array_oid_t parents_arr = GIT_ARRAY_INIT;
+ const git_oid *tree_id;
+
+ assert(tree && git_tree_owner(tree) == repo);
+
+ tree_id = git_tree_id(tree);
+
+ if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
+ return error;
+
+ error = git_commit__create_buffer_internal(
+ out, author, committer,
+ message_encoding, message, tree_id,
+ &parents_arr);
+
+ git_array_clear(parents_arr);
+ return error;
+}
+
+/**
+ * Append to 'out' properly marking continuations when there's a newline in 'content'
+ */
+static void format_header_field(git_buf *out, const char *field, const char *content)
+{
+ const char *lf;
+
+ assert(out && field && content);
+
+ git_buf_puts(out, field);
+ git_buf_putc(out, ' ');
+
+ while ((lf = strchr(content, '\n')) != NULL) {
+ git_buf_put(out, content, lf - content);
+ git_buf_puts(out, "\n ");
+ content = lf + 1;
+ }
+
+ git_buf_puts(out, content);
+ git_buf_putc(out, '\n');
+}
+
+int git_commit_create_with_signature(
+ git_oid *out,
+ git_repository *repo,
+ const char *commit_content,
+ const char *signature,
+ const char *signature_field)
+{
+ git_odb *odb;
+ int error = 0;
+ const char *field;
+ const char *header_end;
+ git_buf commit = GIT_BUF_INIT;
+
+ /* We start by identifying the end of the commit header */
+ header_end = strstr(commit_content, "\n\n");
+ if (!header_end) {
+ giterr_set(GITERR_INVALID, "malformed commit contents");
+ return -1;
+ }
+
+ field = signature_field ? signature_field : "gpgsig";
+
+ /* The header ends after the first LF */
+ header_end++;
+ git_buf_put(&commit, commit_content, header_end - commit_content);
+ format_header_field(&commit, field, signature);
+ git_buf_puts(&commit, header_end);
+
+ if (git_buf_oom(&commit))
+ return -1;
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_buf_free(&commit);
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_commit_h__
+#define INCLUDE_commit_h__
+
+#include "git2/commit.h"
+#include "tree.h"
+#include "repository.h"
+#include "array.h"
+
+#include <time.h>
+
+struct git_commit {
+ git_object object;
+
+ git_array_t(git_oid) parent_ids;
+ git_oid tree_id;
+
+ git_signature *author;
+ git_signature *committer;
+
+ char *message_encoding;
+ char *raw_message;
+ char *raw_header;
+
+ char *summary;
+ char *body;
+};
+
+void git_commit__free(void *commit);
+int git_commit__parse(void *commit, git_odb_object *obj);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "commit_list.h"
+#include "common.h"
+#include "revwalk.h"
+#include "pool.h"
+#include "odb.h"
+
+int git_commit_list_time_cmp(const void *a, const void *b)
+{
+ int64_t time_a = ((git_commit_list_node *) a)->time;
+ int64_t time_b = ((git_commit_list_node *) b)->time;
+
+ if (time_a < time_b)
+ return 1;
+ if (time_a > time_b)
+ return -1;
+
+ return 0;
+}
+
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list *new_list = git__malloc(sizeof(git_commit_list));
+ if (new_list != NULL) {
+ new_list->item = item;
+ new_list->next = *list_p;
+ }
+ *list_p = new_list;
+ return new_list;
+}
+
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list **pp = list_p;
+ git_commit_list *p;
+
+ while ((p = *pp) != NULL) {
+ if (git_commit_list_time_cmp(p->item, item) > 0)
+ break;
+
+ pp = &p->next;
+ }
+
+ return git_commit_list_insert(item, pp);
+}
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)
+{
+ return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1);
+}
+
+static int commit_error(git_commit_list_node *commit, const char *msg)
+{
+ char commit_oid[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(commit_oid, &commit->oid);
+ commit_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_ODB, "Failed to parse commit %s - %s", commit_oid, msg);
+
+ return -1;
+}
+
+static git_commit_list_node **alloc_parents(
+ git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)
+{
+ if (n_parents <= PARENTS_PER_COMMIT)
+ return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));
+
+ return (git_commit_list_node **)git_pool_malloc(
+ &walk->commit_pool, (uint32_t)(n_parents * sizeof(git_commit_list_node *)));
+}
+
+
+void git_commit_list_free(git_commit_list **list_p)
+{
+ git_commit_list *list = *list_p;
+
+ if (list == NULL)
+ return;
+
+ while (list) {
+ git_commit_list *temp = list;
+ list = temp->next;
+ git__free(temp);
+ }
+
+ *list_p = NULL;
+}
+
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
+{
+ git_commit_list *top = *stack;
+ git_commit_list_node *item = top ? top->item : NULL;
+
+ if (top) {
+ *stack = top->next;
+ git__free(top);
+ }
+ return item;
+}
+
+static int commit_quick_parse(
+ git_revwalk *walk,
+ git_commit_list_node *commit,
+ const uint8_t *buffer,
+ size_t buffer_len)
+{
+ const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;
+ const uint8_t *buffer_end = buffer + buffer_len;
+ const uint8_t *parents_start, *committer_start;
+ int i, parents = 0;
+ int64_t commit_time;
+
+ buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;
+
+ parents_start = buffer;
+ while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {
+ parents++;
+ buffer += parent_len;
+ }
+
+ commit->parents = alloc_parents(walk, commit, parents);
+ GITERR_CHECK_ALLOC(commit->parents);
+
+ buffer = parents_start;
+ for (i = 0; i < parents; ++i) {
+ git_oid oid;
+
+ if (git_oid_fromstr(&oid, (const char *)buffer + strlen("parent ")) < 0)
+ return -1;
+
+ commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);
+ if (commit->parents[i] == NULL)
+ return -1;
+
+ buffer += parent_len;
+ }
+
+ commit->out_degree = (unsigned short)parents;
+
+ if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ buffer++;
+
+ if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ /* Skip trailing spaces */
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ /* Seek for the beginning of the pack of digits */
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+
+ /* Skip potential timezone offset */
+ if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) {
+ buffer--;
+
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+ }
+
+ if ((buffer == committer_start) || (git__strtol64(&commit_time, (char *)(buffer + 1), NULL, 10) < 0))
+ return commit_error(commit, "cannot parse commit time");
+
+ commit->time = commit_time;
+ commit->parsed = 1;
+ return 0;
+}
+
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
+{
+ git_odb_object *obj;
+ int error;
+
+ if (commit->parsed)
+ return 0;
+
+ if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
+ return error;
+
+ if (obj->cached.type != GIT_OBJ_COMMIT) {
+ giterr_set(GITERR_INVALID, "Object is no commit object");
+ error = -1;
+ } else
+ error = commit_quick_parse(
+ walk, commit,
+ (const uint8_t *)git_odb_object_data(obj),
+ git_odb_object_size(obj));
+
+ git_odb_object_free(obj);
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_commit_list_h__
+#define INCLUDE_commit_list_h__
+
+#include "git2/oid.h"
+
+#define PARENT1 (1 << 0)
+#define PARENT2 (1 << 1)
+#define RESULT (1 << 2)
+#define STALE (1 << 3)
+#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT)
+
+#define PARENTS_PER_COMMIT 2
+#define COMMIT_ALLOC \
+ (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *))
+
+#define FLAG_BITS 4
+
+typedef struct git_commit_list_node {
+ git_oid oid;
+ int64_t time;
+ unsigned int seen:1,
+ uninteresting:1,
+ topo_delay:1,
+ parsed:1,
+ added:1,
+ flags : FLAG_BITS;
+
+ unsigned short in_degree;
+ unsigned short out_degree;
+
+ struct git_commit_list_node **parents;
+} git_commit_list_node;
+
+typedef struct git_commit_list {
+ git_commit_list_node *item;
+ struct git_commit_list *next;
+} git_commit_list;
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk);
+int git_commit_list_time_cmp(const void *a, const void *b);
+void git_commit_list_free(git_commit_list **list_p);
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p);
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p);
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit);
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_common_h__
+#define INCLUDE_common_h__
+
+#include "git2/common.h"
+#include "cc-compat.h"
+
+/** Declare a function as always inlined. */
+#if defined(_MSC_VER)
+# define GIT_INLINE(type) static __inline type
+#else
+# define GIT_INLINE(type) static inline type
+#endif
+
+/** Support for gcc/clang __has_builtin intrinsic */
+#ifndef __has_builtin
+# define __has_builtin(x) 0
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef GIT_WIN32
+
+# include <io.h>
+# include <direct.h>
+# include <winsock2.h>
+# include <windows.h>
+# include <ws2tcpip.h>
+# include "win32/msvc-compat.h"
+# include "win32/mingw-compat.h"
+# include "win32/win32-compat.h"
+# include "win32/error.h"
+# include "win32/version.h"
+# ifdef GIT_THREADS
+# include "win32/thread.h"
+# endif
+# if defined(GIT_MSVC_CRTDBG)
+# include "win32/w32_stack.h"
+# include "win32/w32_crtdbg_stacktrace.h"
+# endif
+
+#else
+
+# include <unistd.h>
+# include <strings.h>
+# ifdef GIT_THREADS
+# include <pthread.h>
+# include <sched.h>
+# endif
+#define GIT_STDLIB_CALL
+
+#ifdef GIT_USE_STAT_ATIMESPEC
+# define st_atim st_atimespec
+# define st_ctim st_ctimespec
+# define st_mtim st_mtimespec
+#endif
+
+# include <arpa/inet.h>
+
+#endif
+
+#include "git2/types.h"
+#include "git2/errors.h"
+#include "thread-utils.h"
+#include "integer.h"
+
+#include <regex.h>
+
+#define DEFAULT_BUFSIZE 65536
+#define FILEIO_BUFSIZE DEFAULT_BUFSIZE
+#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE
+#define NETIO_BUFSIZE DEFAULT_BUFSIZE
+
+/**
+ * Check a pointer allocation result, returning -1 if it failed.
+ */
+#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
+
+/**
+ * Check a buffer allocation result, returning -1 if it failed.
+ */
+#define GITERR_CHECK_ALLOC_BUF(buf) if ((void *)(buf) == NULL || git_buf_oom(buf)) { return -1; }
+
+/**
+ * Check a return value and propagate result if non-zero.
+ */
+#define GITERR_CHECK_ERROR(code) \
+ do { int _err = (code); if (_err) return _err; } while (0)
+
+/**
+ * Set the error message for this thread, formatting as needed.
+ */
+
+void giterr_set(int error_class, const char *string, ...) GIT_FORMAT_PRINTF(2, 3);
+
+/**
+ * Set the error message for a regex failure, using the internal regex
+ * error code lookup and return a libgit error code.
+ */
+int giterr_set_regex(const regex_t *regex, int error_code);
+
+/**
+ * Set error message for user callback if needed.
+ *
+ * If the error code in non-zero and no error message is set, this
+ * sets a generic error message.
+ *
+ * @return This always returns the `error_code` parameter.
+ */
+GIT_INLINE(int) giterr_set_after_callback_function(
+ int error_code, const char *action)
+{
+ if (error_code) {
+ const git_error *e = giterr_last();
+ if (!e || !e->message)
+ giterr_set(e ? e->klass : GITERR_CALLBACK,
+ "%s callback returned %d", action, error_code);
+ }
+ return error_code;
+}
+
+#ifdef GIT_WIN32
+#define giterr_set_after_callback(code) \
+ giterr_set_after_callback_function((code), __FUNCTION__)
+#else
+#define giterr_set_after_callback(code) \
+ giterr_set_after_callback_function((code), __func__)
+#endif
+
+/**
+ * Gets the system error code for this thread.
+ */
+int giterr_system_last(void);
+
+/**
+ * Sets the system error code for this thread.
+ */
+void giterr_system_set(int code);
+
+/**
+ * Structure to preserve libgit2 error state
+ */
+typedef struct {
+ int error_code;
+ unsigned int oom : 1;
+ git_error error_msg;
+} git_error_state;
+
+/**
+ * Capture current error state to restore later, returning error code.
+ * If `error_code` is zero, this does not clear the current error state.
+ * You must either restore this error state, or free it.
+ */
+extern int giterr_state_capture(git_error_state *state, int error_code);
+
+/**
+ * Restore error state to a previous value, returning saved error code.
+ */
+extern int giterr_state_restore(git_error_state *state);
+
+/** Free an error state. */
+extern void giterr_state_free(git_error_state *state);
+
+/**
+ * Check a versioned structure for validity
+ */
+GIT_INLINE(int) giterr__check_version(const void *structure, unsigned int expected_max, const char *name)
+{
+ unsigned int actual;
+
+ if (!structure)
+ return 0;
+
+ actual = *(const unsigned int*)structure;
+ if (actual > 0 && actual <= expected_max)
+ return 0;
+
+ giterr_set(GITERR_INVALID, "Invalid version %d on %s", actual, name);
+ return -1;
+}
+#define GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) return -1
+
+/**
+ * Initialize a structure with a version.
+ */
+GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version)
+{
+ memset(structure, 0, len);
+ *((int*)structure) = version;
+}
+#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)
+
+#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \
+ TYPE _tmpl = TPL; \
+ GITERR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \
+ memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0)
+
+
+/** Check for additive overflow, setting an error if would occur. */
+#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \
+ (git__add_sizet_overflow(out, one, two) ? (giterr_set_oom(), 1) : 0)
+
+/** Check for additive overflow, setting an error if would occur. */
+#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \
+ (git__multiply_sizet_overflow(out, nelem, elsize) ? (giterr_set_oom(), 1) : 0)
+
+/** Check for additive overflow, failing if it would occur. */
+#define GITERR_CHECK_ALLOC_ADD(out, one, two) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; }
+
+#define GITERR_CHECK_ALLOC_ADD3(out, one, two, three) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; }
+
+#define GITERR_CHECK_ALLOC_ADD4(out, one, two, three, four) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; }
+
+/** Check for multiplicative overflow, failing if it would occur. */
+#define GITERR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; }
+
+/* NOTE: other giterr functions are in the public errors.h header file */
+
+#include "util.h"
+
+#endif /* INCLUDE_common_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "sysdir.h"
+#include "config.h"
+#include "git2/config.h"
+#include "git2/sys/config.h"
+#include "vector.h"
+#include "buf_text.h"
+#include "config_file.h"
+#include "transaction.h"
+#if GIT_WIN32
+# include <windows.h>
+#endif
+
+#include <ctype.h>
+
+void git_config_entry_free(git_config_entry *entry)
+{
+ if (!entry)
+ return;
+
+ entry->free(entry);
+}
+
+typedef struct {
+ git_refcount rc;
+
+ git_config_backend *file;
+ git_config_level_t level;
+} file_internal;
+
+static void file_internal_free(file_internal *internal)
+{
+ git_config_backend *file;
+
+ file = internal->file;
+ file->free(file);
+ git__free(internal);
+}
+
+static void config_free(git_config *cfg)
+{
+ size_t i;
+ file_internal *internal;
+
+ for (i = 0; i < cfg->files.length; ++i) {
+ internal = git_vector_get(&cfg->files, i);
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
+ }
+
+ git_vector_free(&cfg->files);
+
+ git__memzero(cfg, sizeof(*cfg));
+ git__free(cfg);
+}
+
+void git_config_free(git_config *cfg)
+{
+ if (cfg == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(cfg, config_free);
+}
+
+static int config_backend_cmp(const void *a, const void *b)
+{
+ const file_internal *bk_a = (const file_internal *)(a);
+ const file_internal *bk_b = (const file_internal *)(b);
+
+ return bk_b->level - bk_a->level;
+}
+
+int git_config_new(git_config **out)
+{
+ git_config *cfg;
+
+ cfg = git__malloc(sizeof(git_config));
+ GITERR_CHECK_ALLOC(cfg);
+
+ memset(cfg, 0x0, sizeof(git_config));
+
+ if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) {
+ git__free(cfg);
+ return -1;
+ }
+
+ *out = cfg;
+ GIT_REFCOUNT_INC(cfg);
+ return 0;
+}
+
+int git_config_add_file_ondisk(
+ git_config *cfg,
+ const char *path,
+ git_config_level_t level,
+ int force)
+{
+ git_config_backend *file = NULL;
+ struct stat st;
+ int res;
+
+ assert(cfg && path);
+
+ res = p_stat(path, &st);
+ if (res < 0 && errno != ENOENT) {
+ giterr_set(GITERR_CONFIG, "Error stat'ing config file '%s'", path);
+ return -1;
+ }
+
+ if (git_config_file__ondisk(&file, path) < 0)
+ return -1;
+
+ if ((res = git_config_add_backend(cfg, file, level, force)) < 0) {
+ /*
+ * free manually; the file is not owned by the config
+ * instance yet and will not be freed on cleanup
+ */
+ file->free(file);
+ return res;
+ }
+
+ return 0;
+}
+
+int git_config_open_ondisk(git_config **out, const char *path)
+{
+ int error;
+ git_config *config;
+
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
+ return -1;
+
+ if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
+}
+
+int git_config_snapshot(git_config **out, git_config *in)
+{
+ int error = 0;
+ size_t i;
+ file_internal *internal;
+ git_config *config;
+
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
+ return -1;
+
+ git_vector_foreach(&in->files, i, internal) {
+ git_config_backend *b;
+
+ if ((error = internal->file->snapshot(&b, internal->file)) < 0)
+ break;
+
+ if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) {
+ b->free(b);
+ break;
+ }
+ }
+
+ if (error < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
+}
+
+static int find_internal_file_by_level(
+ file_internal **internal_out,
+ const git_config *cfg,
+ git_config_level_t level)
+{
+ int pos = -1;
+ file_internal *internal;
+ size_t i;
+
+ /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
+ * which has the highest level. As config files are stored in a vector
+ * sorted by decreasing order of level, getting the file at position 0
+ * will do the job.
+ */
+ if (level == GIT_CONFIG_HIGHEST_LEVEL) {
+ pos = 0;
+ } else {
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == level)
+ pos = (int)i;
+ }
+ }
+
+ if (pos == -1) {
+ giterr_set(GITERR_CONFIG,
+ "No config file exists for the given level '%i'", (int)level);
+ return GIT_ENOTFOUND;
+ }
+
+ *internal_out = git_vector_get(&cfg->files, pos);
+
+ return 0;
+}
+
+static int duplicate_level(void **old_raw, void *new_raw)
+{
+ file_internal **old = (file_internal **)old_raw;
+
+ GIT_UNUSED(new_raw);
+
+ giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (int)(*old)->level);
+ return GIT_EEXISTS;
+}
+
+static void try_remove_existing_file_internal(
+ git_config *cfg,
+ git_config_level_t level)
+{
+ int pos = -1;
+ file_internal *internal;
+ size_t i;
+
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == level)
+ pos = (int)i;
+ }
+
+ if (pos == -1)
+ return;
+
+ internal = git_vector_get(&cfg->files, pos);
+
+ if (git_vector_remove(&cfg->files, pos) < 0)
+ return;
+
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
+}
+
+static int git_config__add_internal(
+ git_config *cfg,
+ file_internal *internal,
+ git_config_level_t level,
+ int force)
+{
+ int result;
+
+ /* delete existing config file for level if it exists */
+ if (force)
+ try_remove_existing_file_internal(cfg, level);
+
+ if ((result = git_vector_insert_sorted(&cfg->files,
+ internal, &duplicate_level)) < 0)
+ return result;
+
+ git_vector_sort(&cfg->files);
+ internal->file->cfg = cfg;
+
+ GIT_REFCOUNT_INC(internal);
+
+ return 0;
+}
+
+int git_config_open_global(git_config **cfg_out, git_config *cfg)
+{
+ if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG))
+ return 0;
+
+ return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL);
+}
+
+int git_config_open_level(
+ git_config **cfg_out,
+ const git_config *cfg_parent,
+ git_config_level_t level)
+{
+ git_config *cfg;
+ file_internal *internal;
+ int res;
+
+ if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0)
+ return res;
+
+ if ((res = git_config_new(&cfg)) < 0)
+ return res;
+
+ if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) {
+ git_config_free(cfg);
+ return res;
+ }
+
+ *cfg_out = cfg;
+
+ return 0;
+}
+
+int git_config_add_backend(
+ git_config *cfg,
+ git_config_backend *file,
+ git_config_level_t level,
+ int force)
+{
+ file_internal *internal;
+ int result;
+
+ assert(cfg && file);
+
+ GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
+
+ if ((result = file->open(file, level)) < 0)
+ return result;
+
+ internal = git__malloc(sizeof(file_internal));
+ GITERR_CHECK_ALLOC(internal);
+
+ memset(internal, 0x0, sizeof(file_internal));
+
+ internal->file = file;
+ internal->level = level;
+
+ if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) {
+ git__free(internal);
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * Loop over all the variables
+ */
+
+typedef struct {
+ git_config_iterator parent;
+ git_config_iterator *current;
+ const git_config *cfg;
+ regex_t regex;
+ size_t i;
+} all_iter;
+
+static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
+{
+ file_internal *internal;
+
+ for (; i > 0; --i) {
+ internal = git_vector_get(&cfg->files, i - 1);
+ if (!internal || !internal->file)
+ continue;
+
+ *out = i;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+ file_internal *internal;
+ git_config_backend *backend;
+ size_t i;
+ int error = 0;
+
+ if (iter->current != NULL &&
+ (error = iter->current->next(entry, iter->current)) == 0) {
+ return 0;
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ return error;
+
+ do {
+ if (find_next_backend(&i, iter->cfg, iter->i) < 0)
+ return GIT_ITEROVER;
+
+ internal = git_vector_get(&iter->cfg->files, i - 1);
+ backend = internal->file;
+ iter->i = i - 1;
+
+ if (iter->current)
+ iter->current->free(iter->current);
+
+ iter->current = NULL;
+ error = backend->iterator(&iter->current, backend);
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ if (error < 0)
+ return error;
+
+ error = iter->current->next(entry, iter->current);
+ /* If this backend is empty, then keep going */
+ if (error == GIT_ITEROVER)
+ continue;
+
+ return error;
+
+ } while(1);
+
+ return GIT_ITEROVER;
+}
+
+static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ int error;
+ all_iter *iter = (all_iter *) _iter;
+
+ /*
+ * We use the "normal" function to grab the next one across
+ * backends and then apply the regex
+ */
+ while ((error = all_iter_next(entry, _iter)) == 0) {
+ /* skip non-matching keys if regexp was provided */
+ if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0)
+ continue;
+
+ /* and simply return if we like the entry's name */
+ return 0;
+ }
+
+ return error;
+}
+
+static void all_iter_free(git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+
+ if (iter->current)
+ iter->current->free(iter->current);
+
+ git__free(iter);
+}
+
+static void all_iter_glob_free(git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+
+ regfree(&iter->regex);
+ all_iter_free(_iter);
+}
+
+int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
+{
+ all_iter *iter;
+
+ iter = git__calloc(1, sizeof(all_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->parent.free = all_iter_free;
+ iter->parent.next = all_iter_next;
+
+ iter->i = cfg->files.length;
+ iter->cfg = cfg;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
+}
+
+int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp)
+{
+ all_iter *iter;
+ int result;
+
+ if (regexp == NULL)
+ return git_config_iterator_new(out, cfg);
+
+ iter = git__calloc(1, sizeof(all_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if ((result = p_regcomp(&iter->regex, regexp, REG_EXTENDED)) != 0) {
+ giterr_set_regex(&iter->regex, result);
+ git__free(iter);
+ return -1;
+ }
+
+ iter->parent.next = all_iter_glob_next;
+ iter->parent.free = all_iter_glob_free;
+ iter->i = cfg->files.length;
+ iter->cfg = cfg;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
+}
+
+int git_config_foreach(
+ const git_config *cfg, git_config_foreach_cb cb, void *payload)
+{
+ return git_config_foreach_match(cfg, NULL, cb, payload);
+}
+
+int git_config_backend_foreach_match(
+ git_config_backend *backend,
+ const char *regexp,
+ git_config_foreach_cb cb,
+ void *payload)
+{
+ git_config_entry *entry;
+ git_config_iterator* iter;
+ regex_t regex;
+ int error = 0;
+
+ if (regexp != NULL) {
+ if ((error = p_regcomp(®ex, regexp, REG_EXTENDED)) != 0) {
+ giterr_set_regex(®ex, error);
+ regfree(®ex);
+ return -1;
+ }
+ }
+
+ if ((error = backend->iterator(&iter, backend)) < 0) {
+ iter = NULL;
+ return -1;
+ }
+
+ while (!(iter->next(&entry, iter) < 0)) {
+ /* skip non-matching keys if regexp was provided */
+ if (regexp && regexec(®ex, entry->name, 0, NULL, 0) != 0)
+ continue;
+
+ /* abort iterator on non-zero return value */
+ if ((error = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (regexp != NULL)
+ regfree(®ex);
+
+ iter->free(iter);
+
+ return error;
+}
+
+int git_config_foreach_match(
+ const git_config *cfg,
+ const char *regexp,
+ git_config_foreach_cb cb,
+ void *payload)
+{
+ int error;
+ git_config_iterator *iter;
+ git_config_entry *entry;
+
+ if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0)
+ return error;
+
+ while (!(error = git_config_next(&entry, iter))) {
+ if ((error = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ git_config_iterator_free(iter);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+/**************
+ * Setters
+ **************/
+
+static int config_error_nofiles(const char *name)
+{
+ giterr_set(GITERR_CONFIG,
+ "Cannot set value for '%s' when no config files exist", name);
+ return GIT_ENOTFOUND;
+}
+
+int git_config_delete_entry(git_config *cfg, const char *name)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ return file->del(file, name);
+}
+
+int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
+{
+ char str_value[32]; /* All numbers should fit in here */
+ p_snprintf(str_value, sizeof(str_value), "%" PRId64, value);
+ return git_config_set_string(cfg, name, str_value);
+}
+
+int git_config_set_int32(git_config *cfg, const char *name, int32_t value)
+{
+ return git_config_set_int64(cfg, name, (int64_t)value);
+}
+
+int git_config_set_bool(git_config *cfg, const char *name, int value)
+{
+ return git_config_set_string(cfg, name, value ? "true" : "false");
+}
+
+int git_config_set_string(git_config *cfg, const char *name, const char *value)
+{
+ int error;
+ git_config_backend *file;
+ file_internal *internal;
+
+ if (!value) {
+ giterr_set(GITERR_CONFIG, "The value to set cannot be NULL");
+ return -1;
+ }
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ error = file->set(file, name, value);
+
+ if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
+ git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+
+ return error;
+}
+
+int git_config__update_entry(
+ git_config *config,
+ const char *key,
+ const char *value,
+ bool overwrite_existing,
+ bool only_if_existing)
+{
+ int error = 0;
+ git_config_entry *ce = NULL;
+
+ if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0)
+ return error;
+
+ if (!ce && only_if_existing) /* entry doesn't exist */
+ return 0;
+ if (ce && !overwrite_existing) /* entry would be overwritten */
+ return 0;
+ if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */
+ return 0;
+ if (!value && (!ce || !ce->value)) /* asked to delete absent entry */
+ return 0;
+
+ if (!value)
+ error = git_config_delete_entry(config, key);
+ else
+ error = git_config_set_string(config, key, value);
+
+ git_config_entry_free(ce);
+ return error;
+}
+
+/***********
+ * Getters
+ ***********/
+
+static int config_error_notfound(const char *name)
+{
+ giterr_set(GITERR_CONFIG, "Config value '%s' was not found", name);
+ return GIT_ENOTFOUND;
+}
+
+enum {
+ GET_ALL_ERRORS = 0,
+ GET_NO_MISSING = 1,
+ GET_NO_ERRORS = 2
+};
+
+static int get_entry(
+ git_config_entry **out,
+ const git_config *cfg,
+ const char *name,
+ bool normalize_name,
+ int want_errors)
+{
+ int res = GIT_ENOTFOUND;
+ const char *key = name;
+ char *normalized = NULL;
+ size_t i;
+ file_internal *internal;
+
+ *out = NULL;
+
+ if (normalize_name) {
+ if ((res = git_config__normalize_name(name, &normalized)) < 0)
+ goto cleanup;
+ key = normalized;
+ }
+
+ res = GIT_ENOTFOUND;
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (!internal || !internal->file)
+ continue;
+
+ res = internal->file->get(internal->file, key, out);
+ if (res != GIT_ENOTFOUND)
+ break;
+ }
+
+ git__free(normalized);
+
+cleanup:
+ if (res == GIT_ENOTFOUND)
+ res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name);
+ else if (res && (want_errors == GET_NO_ERRORS)) {
+ giterr_clear();
+ res = 0;
+ }
+
+ return res;
+}
+
+int git_config_get_entry(
+ git_config_entry **out, const git_config *cfg, const char *name)
+{
+ return get_entry(out, cfg, name, true, GET_ALL_ERRORS);
+}
+
+int git_config__lookup_entry(
+ git_config_entry **out,
+ const git_config *cfg,
+ const char *key,
+ bool no_errors)
+{
+ return get_entry(
+ out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING);
+}
+
+int git_config_get_mapped(
+ int *out,
+ const git_config *cfg,
+ const char *name,
+ const git_cvar_map *maps,
+ size_t map_n)
+{
+ git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ ret = git_config_lookup_map_value(out, maps, map_n, entry->value);
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ ret = git_config_parse_int64(out, entry->value);
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ ret = git_config_parse_int32(out, entry->value);
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+int git_config_get_bool(int *out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ ret = git_config_parse_bool(out, entry->value);
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+static int is_readonly(const git_config *cfg)
+{
+ size_t i;
+ file_internal *internal;
+
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (!internal || !internal->file)
+ continue;
+
+ if (!internal->file->readonly)
+ return 0;
+ }
+
+ return 1;
+}
+
+int git_config_get_path(git_buf *out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int error;
+
+ if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return error;
+
+ error = git_config_parse_path(out, entry->value);
+ git_config_entry_free(entry);
+
+ return error;
+}
+
+int git_config_get_string(
+ const char **out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int ret;
+
+ if (!is_readonly(cfg)) {
+ giterr_set(GITERR_CONFIG, "get_string called on a live config object");
+ return -1;
+ }
+
+ ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS);
+ *out = !ret ? (entry->value ? entry->value : "") : NULL;
+
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+int git_config_get_string_buf(
+ git_buf *out, const git_config *cfg, const char *name)
+{
+ git_config_entry *entry;
+ int ret;
+ const char *str;
+
+ git_buf_sanitize(out);
+
+ ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS);
+ str = !ret ? (entry->value ? entry->value : "") : NULL;
+
+ if (str)
+ ret = git_buf_puts(out, str);
+
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+char *git_config__get_string_force(
+ const git_config *cfg, const char *key, const char *fallback_value)
+{
+ git_config_entry *entry;
+ char *ret;
+
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+ ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL;
+ git_config_entry_free(entry);
+
+ return ret;
+}
+
+int git_config__get_bool_force(
+ const git_config *cfg, const char *key, int fallback_value)
+{
+ int val = fallback_value;
+ git_config_entry *entry;
+
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+
+ if (entry && git_config_parse_bool(&val, entry->value) < 0)
+ giterr_clear();
+
+ git_config_entry_free(entry);
+ return val;
+}
+
+int git_config__get_int_force(
+ const git_config *cfg, const char *key, int fallback_value)
+{
+ int32_t val = (int32_t)fallback_value;
+ git_config_entry *entry;
+
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+
+ if (entry && git_config_parse_int32(&val, entry->value) < 0)
+ giterr_clear();
+
+ git_config_entry_free(entry);
+ return (int)val;
+}
+
+int git_config_get_multivar_foreach(
+ const git_config *cfg, const char *name, const char *regexp,
+ git_config_foreach_cb cb, void *payload)
+{
+ int err, found;
+ git_config_iterator *iter;
+ git_config_entry *entry;
+
+ if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0)
+ return err;
+
+ found = 0;
+ while ((err = iter->next(&entry, iter)) == 0) {
+ found = 1;
+
+ if ((err = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(err);
+ break;
+ }
+ }
+
+ iter->free(iter);
+ if (err == GIT_ITEROVER)
+ err = 0;
+
+ if (found == 0 && err == 0)
+ err = config_error_notfound(name);
+
+ return err;
+}
+
+typedef struct {
+ git_config_iterator parent;
+ git_config_iterator *iter;
+ char *name;
+ regex_t regex;
+ int have_regex;
+} multivar_iter;
+
+static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ multivar_iter *iter = (multivar_iter *) _iter;
+ int error = 0;
+
+ while ((error = iter->iter->next(entry, iter->iter)) == 0) {
+ if (git__strcmp(iter->name, (*entry)->name))
+ continue;
+
+ if (!iter->have_regex)
+ return 0;
+
+ if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0)
+ return 0;
+ }
+
+ return error;
+}
+
+void multivar_iter_free(git_config_iterator *_iter)
+{
+ multivar_iter *iter = (multivar_iter *) _iter;
+
+ iter->iter->free(iter->iter);
+
+ git__free(iter->name);
+ if (iter->have_regex)
+ regfree(&iter->regex);
+ git__free(iter);
+}
+
+int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp)
+{
+ multivar_iter *iter = NULL;
+ git_config_iterator *inner = NULL;
+ int error;
+
+ if ((error = git_config_iterator_new(&inner, cfg)) < 0)
+ return error;
+
+ iter = git__calloc(1, sizeof(multivar_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if ((error = git_config__normalize_name(name, &iter->name)) < 0)
+ goto on_error;
+
+ if (regexp != NULL) {
+ error = p_regcomp(&iter->regex, regexp, REG_EXTENDED);
+ if (error != 0) {
+ giterr_set_regex(&iter->regex, error);
+ error = -1;
+ regfree(&iter->regex);
+ goto on_error;
+ }
+
+ iter->have_regex = 1;
+ }
+
+ iter->iter = inner;
+ iter->parent.free = multivar_iter_free;
+ iter->parent.next = multivar_iter_next;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
+
+on_error:
+
+ inner->free(inner);
+ git__free(iter);
+ return error;
+}
+
+int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ return file->set_multivar(file, name, regexp, value);
+}
+
+int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ return file->del_multivar(file, name, regexp);
+}
+
+int git_config_next(git_config_entry **entry, git_config_iterator *iter)
+{
+ return iter->next(entry, iter);
+}
+
+void git_config_iterator_free(git_config_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ iter->free(iter);
+}
+
+int git_config_find_global(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL);
+}
+
+int git_config_find_xdg(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);
+}
+
+int git_config_find_system(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
+}
+
+int git_config_find_programdata(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA);
+}
+
+int git_config__global_location(git_buf *buf)
+{
+ const git_buf *paths;
+ const char *sep, *start;
+
+ if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0)
+ return -1;
+
+ /* no paths, so give up */
+ if (!paths || !git_buf_len(paths))
+ return -1;
+
+ /* find unescaped separator or end of string */
+ for (sep = start = git_buf_cstr(paths); *sep; ++sep) {
+ if (*sep == GIT_PATH_LIST_SEPARATOR &&
+ (sep <= start || sep[-1] != '\\'))
+ break;
+ }
+
+ if (git_buf_set(buf, start, (size_t)(sep - start)) < 0)
+ return -1;
+
+ return git_buf_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL);
+}
+
+int git_config_open_default(git_config **out)
+{
+ int error;
+ git_config *cfg = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
+
+ if (!git_config_find_global(&buf) || !git_config__global_location(&buf)) {
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_GLOBAL, 0);
+ }
+
+ if (!error && !git_config_find_xdg(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_XDG, 0);
+
+ if (!error && !git_config_find_system(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_SYSTEM, 0);
+
+ if (!error && !git_config_find_programdata(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_PROGRAMDATA, 0);
+
+ git_buf_free(&buf);
+
+ if (error) {
+ git_config_free(cfg);
+ cfg = NULL;
+ }
+
+ *out = cfg;
+
+ return error;
+}
+
+int git_config_lock(git_transaction **out, git_config *cfg)
+{
+ int error;
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file) {
+ giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files");
+ return -1;
+ }
+ file = internal->file;
+
+ if ((error = file->lock(file)) < 0)
+ return error;
+
+ return git_transaction_config_new(out, cfg);
+}
+
+int git_config_unlock(git_config *cfg, int commit)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file) {
+ giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files");
+ return -1;
+ }
+
+ file = internal->file;
+
+ return file->unlock(file, commit);
+}
+
+/***********
+ * Parsers
+ ***********/
+
+int git_config_lookup_map_value(
+ int *out,
+ const git_cvar_map *maps,
+ size_t map_n,
+ const char *value)
+{
+ size_t i;
+
+ if (!value)
+ goto fail_parse;
+
+ for (i = 0; i < map_n; ++i) {
+ const git_cvar_map *m = maps + i;
+
+ switch (m->cvar_type) {
+ case GIT_CVAR_FALSE:
+ case GIT_CVAR_TRUE: {
+ int bool_val;
+
+ if (git__parse_bool(&bool_val, value) == 0 &&
+ bool_val == (int)m->cvar_type) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
+
+ case GIT_CVAR_INT32:
+ if (git_config_parse_int32(out, value) == 0)
+ return 0;
+ break;
+
+ case GIT_CVAR_STRING:
+ if (strcasecmp(value, m->str_match) == 0) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
+ }
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to map '%s'", value);
+ return -1;
+}
+
+int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out,
+ const git_cvar_map *maps, size_t map_n, int enum_val)
+{
+ size_t i;
+
+ for (i = 0; i < map_n; i++) {
+ const git_cvar_map *m = &maps[i];
+
+ if (m->map_value != enum_val)
+ continue;
+
+ *type_out = m->cvar_type;
+ *str_out = m->str_match;
+ return 0;
+ }
+
+ giterr_set(GITERR_CONFIG, "invalid enum value");
+ return GIT_ENOTFOUND;
+}
+
+int git_config_parse_bool(int *out, const char *value)
+{
+ if (git__parse_bool(out, value) == 0)
+ return 0;
+
+ if (git_config_parse_int32(out, value) == 0) {
+ *out = !!(*out);
+ return 0;
+ }
+
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
+ return -1;
+}
+
+int git_config_parse_int64(int64_t *out, const char *value)
+{
+ const char *num_end;
+ int64_t num;
+
+ if (!value || git__strtol64(&num, value, &num_end, 0) < 0)
+ goto fail_parse;
+
+ switch (*num_end) {
+ case 'g':
+ case 'G':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'm':
+ case 'M':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'k':
+ case 'K':
+ num *= 1024;
+
+ /* check that that there are no more characters after the
+ * given modifier suffix */
+ if (num_end[1] != '\0')
+ return -1;
+
+ /* fallthrough */
+
+ case '\0':
+ *out = num;
+ return 0;
+
+ default:
+ goto fail_parse;
+ }
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value ? value : "(null)");
+ return -1;
+}
+
+int git_config_parse_int32(int32_t *out, const char *value)
+{
+ int64_t tmp;
+ int32_t truncate;
+
+ if (git_config_parse_int64(&tmp, value) < 0)
+ goto fail_parse;
+
+ truncate = tmp & 0xFFFFFFFF;
+ if (truncate != tmp)
+ goto fail_parse;
+
+ *out = truncate;
+ return 0;
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value ? value : "(null)");
+ return -1;
+}
+
+int git_config_parse_path(git_buf *out, const char *value)
+{
+ int error = 0;
+ const git_buf *home;
+
+ assert(out && value);
+
+ git_buf_sanitize(out);
+
+ if (value[0] == '~') {
+ if (value[1] != '\0' && value[1] != '/') {
+ giterr_set(GITERR_CONFIG, "retrieving a homedir by name is not supported");
+ return -1;
+ }
+
+ if ((error = git_sysdir_get(&home, GIT_SYSDIR_GLOBAL)) < 0)
+ return error;
+
+ git_buf_sets(out, home->ptr);
+ git_buf_puts(out, value + 1);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ return 0;
+ }
+
+ return git_buf_sets(out, value);
+}
+
+/* Take something the user gave us and make it nice for our hash function */
+int git_config__normalize_name(const char *in, char **out)
+{
+ char *name, *fdot, *ldot;
+
+ assert(in && out);
+
+ name = git__strdup(in);
+ GITERR_CHECK_ALLOC(name);
+
+ fdot = strchr(name, '.');
+ ldot = strrchr(name, '.');
+
+ if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
+ goto invalid;
+
+ /* Validate and downcase up to first dot and after last dot */
+ if (git_config_file_normalize_section(name, fdot) < 0 ||
+ git_config_file_normalize_section(ldot + 1, NULL) < 0)
+ goto invalid;
+
+ /* If there is a middle range, make sure it doesn't have newlines */
+ while (fdot < ldot)
+ if (*fdot++ == '\n')
+ goto invalid;
+
+ *out = name;
+ return 0;
+
+invalid:
+ git__free(name);
+ giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
+ return GIT_EINVALIDSPEC;
+}
+
+struct rename_data {
+ git_config *config;
+ git_buf *name;
+ size_t old_len;
+};
+
+static int rename_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ int error = 0;
+ struct rename_data *data = (struct rename_data *)payload;
+ size_t base_len = git_buf_len(data->name);
+
+ if (base_len > 0 &&
+ !(error = git_buf_puts(data->name, entry->name + data->old_len)))
+ {
+ error = git_config_set_string(
+ data->config, git_buf_cstr(data->name), entry->value);
+
+ git_buf_truncate(data->name, base_len);
+ }
+
+ if (!error)
+ error = git_config_delete_entry(data->config, entry->name);
+
+ return error;
+}
+
+int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name,
+ const char *new_section_name)
+{
+ git_config *config;
+ git_buf pattern = GIT_BUF_INIT, replace = GIT_BUF_INIT;
+ int error = 0;
+ struct rename_data data;
+
+ git_buf_text_puts_escape_regex(&pattern, old_section_name);
+
+ if ((error = git_buf_puts(&pattern, "\\..+")) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ goto cleanup;
+
+ data.config = config;
+ data.name = &replace;
+ data.old_len = strlen(old_section_name) + 1;
+
+ if ((error = git_buf_join(&replace, '.', new_section_name, "")) < 0)
+ goto cleanup;
+
+ if (new_section_name != NULL &&
+ (error = git_config_file_normalize_section(
+ replace.ptr, strchr(replace.ptr, '.'))) < 0)
+ {
+ giterr_set(
+ GITERR_CONFIG, "Invalid config section '%s'", new_section_name);
+ goto cleanup;
+ }
+
+ error = git_config_foreach_match(
+ config, git_buf_cstr(&pattern), rename_config_entries_cb, &data);
+
+cleanup:
+ git_buf_free(&pattern);
+ git_buf_free(&replace);
+
+ return error;
+}
+
+int git_config_init_backend(git_config_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_config_h__
+#define INCLUDE_config_h__
+
+#include "git2.h"
+#include "git2/config.h"
+#include "vector.h"
+#include "repository.h"
+
+#define GIT_CONFIG_FILENAME_PROGRAMDATA "config"
+#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
+#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"
+#define GIT_CONFIG_FILENAME_XDG "config"
+
+#define GIT_CONFIG_FILENAME_INREPO "config"
+#define GIT_CONFIG_FILE_MODE 0666
+
+struct git_config {
+ git_refcount rc;
+ git_vector files;
+};
+
+extern int git_config__global_location(git_buf *buf);
+
+extern int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name, /* eg "branch.dummy" */
+ const char *new_section_name); /* NULL to drop the old section */
+
+/**
+ * Create a configuration file backend for ondisk files
+ *
+ * These are the normal `.gitconfig` files that Core Git
+ * processes. Note that you first have to add this file to a
+ * configuration object before you can query it for configuration
+ * variables.
+ *
+ * @param out the new backend
+ * @param path where the config file is located
+ */
+extern int git_config_file__ondisk(git_config_backend **out, const char *path);
+
+extern int git_config__normalize_name(const char *in, char **out);
+
+/* internal only: does not normalize key and sets out to NULL if not found */
+extern int git_config__lookup_entry(
+ git_config_entry **out,
+ const git_config *cfg,
+ const char *key,
+ bool no_errors);
+
+/* internal only: update and/or delete entry string with constraints */
+extern int git_config__update_entry(
+ git_config *cfg,
+ const char *key,
+ const char *value,
+ bool overwrite_existing,
+ bool only_if_existing);
+
+/*
+ * Lookup functions that cannot fail. These functions look up a config
+ * value and return a fallback value if the value is missing or if any
+ * failures occur while trying to access the value.
+ */
+
+extern char *git_config__get_string_force(
+ const git_config *cfg, const char *key, const char *fallback_value);
+
+extern int git_config__get_bool_force(
+ const git_config *cfg, const char *key, int fallback_value);
+
+extern int git_config__get_int_force(
+ const git_config *cfg, const char *key, int fallback_value);
+
+/* API for repository cvar-style lookups from config - not cached, but
+ * uses cvar value maps and fallbacks
+ */
+extern int git_config__cvar(
+ int *out, git_config *config, git_cvar_cached cvar);
+
+/**
+ * The opposite of git_config_lookup_map_value, we take an enum value
+ * and map it to the string or bool value on the config.
+ */
+int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out,
+ const git_cvar_map *maps, size_t map_n, int enum_val);
+
+/**
+ * Unlock the backend with the highest priority
+ *
+ * Unlocking will allow other writers to updat the configuration
+ * file. Optionally, any changes performed since the lock will be
+ * applied to the configuration.
+ *
+ * @param cfg the configuration
+ * @param commit boolean which indicates whether to commit any changes
+ * done since locking
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "fileops.h"
+#include "repository.h"
+#include "config.h"
+#include "git2/config.h"
+#include "vector.h"
+#include "filter.h"
+
+struct map_data {
+ const char *cvar_name;
+ git_cvar_map *maps;
+ size_t map_count;
+ int default_value;
+};
+
+/*
+ * core.eol
+ * Sets the line ending type to use in the working directory for
+ * files that have the text property set. Alternatives are lf, crlf
+ * and native, which uses the platform's native line ending. The default
+ * value is native. See gitattributes(5) for more information on
+ * end-of-line conversion.
+ */
+static git_cvar_map _cvar_map_eol[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET},
+ {GIT_CVAR_STRING, "lf", GIT_EOL_LF},
+ {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF},
+ {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE}
+};
+
+/*
+ * core.autocrlf
+ * Setting this variable to "true" is almost the same as setting
+ * the text attribute to "auto" on all files except that text files are
+ * not guaranteed to be normalized: files that contain CRLF in the
+ * repository will not be touched. Use this setting if you want to have
+ * CRLF line endings in your working directory even though the repository
+ * does not have normalized line endings. This variable can be set to input,
+ * in which case no output conversion is performed.
+ */
+static git_cvar_map _cvar_map_autocrlf[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
+ {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
+ {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}
+};
+
+static git_cvar_map _cvar_map_safecrlf[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE},
+ {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL},
+ {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN}
+};
+
+/*
+ * Generic map for integer values
+ */
+static git_cvar_map _cvar_map_int[] = {
+ {GIT_CVAR_INT32, NULL, 0},
+};
+
+static struct map_data _cvar_maps[] = {
+ {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT},
+ {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT},
+ {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT },
+ {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT },
+ {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT },
+ {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
+ {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
+ {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT },
+ {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT },
+ {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT},
+ {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT },
+ {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT },
+ {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
+};
+
+int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar)
+{
+ int error = 0;
+ struct map_data *data = &_cvar_maps[(int)cvar];
+ git_config_entry *entry;
+
+ if ((error = git_config__lookup_entry(&entry, config, data->cvar_name, false)) < 0)
+ return error;
+
+ if (!entry)
+ *out = data->default_value;
+ else if (data->maps)
+ error = git_config_lookup_map_value(
+ out, data->maps, data->map_count, entry->value);
+ else
+ error = git_config_parse_bool(out, entry->value);
+
+ git_config_entry_free(entry);
+ return error;
+}
+
+int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
+{
+ *out = repo->cvar_cache[(int)cvar];
+
+ if (*out == GIT_CVAR_NOT_CACHED) {
+ int error;
+ git_config *config;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0 ||
+ (error = git_config__cvar(out, config, cvar)) < 0)
+ return error;
+
+ repo->cvar_cache[(int)cvar] = *out;
+ }
+
+ return 0;
+}
+
+void git_repository__cvar_cache_clear(git_repository *repo)
+{
+ int i;
+
+ for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i)
+ repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "config.h"
+#include "filebuf.h"
+#include "sysdir.h"
+#include "buffer.h"
+#include "buf_text.h"
+#include "git2/config.h"
+#include "git2/sys/config.h"
+#include "git2/types.h"
+#include "strmap.h"
+#include "array.h"
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+
+GIT__USE_STRMAP
+
+typedef struct cvar_t {
+ struct cvar_t *next;
+ git_config_entry *entry;
+ bool included; /* whether this is part of [include] */
+} cvar_t;
+
+typedef struct git_config_file_iter {
+ git_config_iterator parent;
+ git_strmap_iter iter;
+ cvar_t* next_var;
+} git_config_file_iter;
+
+/* Max depth for [include] directives */
+#define MAX_INCLUDE_DEPTH 10
+
+#define CVAR_LIST_HEAD(list) ((list)->head)
+
+#define CVAR_LIST_TAIL(list) ((list)->tail)
+
+#define CVAR_LIST_NEXT(var) ((var)->next)
+
+#define CVAR_LIST_EMPTY(list) ((list)->head == NULL)
+
+#define CVAR_LIST_APPEND(list, var) do {\
+ if (CVAR_LIST_EMPTY(list)) {\
+ CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
+ } else {\
+ CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
+ CVAR_LIST_TAIL(list) = var;\
+ }\
+} while(0)
+
+#define CVAR_LIST_REMOVE_HEAD(list) do {\
+ CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
+} while(0)
+
+#define CVAR_LIST_REMOVE_AFTER(var) do {\
+ CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
+} while(0)
+
+#define CVAR_LIST_FOREACH(list, iter)\
+ for ((iter) = CVAR_LIST_HEAD(list);\
+ (iter) != NULL;\
+ (iter) = CVAR_LIST_NEXT(iter))
+
+/*
+ * Inspired by the FreeBSD functions
+ */
+#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
+ for ((iter) = CVAR_LIST_HEAD(vars);\
+ (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
+ (iter) = (tmp))
+
+struct reader {
+ git_oid checksum;
+ char *file_path;
+ git_buf buffer;
+ char *read_ptr;
+ int line_number;
+ int eof;
+};
+
+typedef struct {
+ git_atomic refcount;
+ git_strmap *values;
+} refcounted_strmap;
+
+typedef struct {
+ git_config_backend parent;
+ /* mutex to coordinate accessing the values */
+ git_mutex values_mutex;
+ refcounted_strmap *values;
+} diskfile_header;
+
+typedef struct {
+ diskfile_header header;
+
+ git_config_level_t level;
+
+ git_array_t(struct reader) readers;
+
+ bool locked;
+ git_filebuf locked_buf;
+ git_buf locked_content;
+
+ char *file_path;
+} diskfile_backend;
+
+typedef struct {
+ diskfile_header header;
+
+ diskfile_backend *snapshot_from;
+} diskfile_readonly_backend;
+
+static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
+static char *escape_value(const char *ptr);
+
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
+static int config_snapshot(git_config_backend **out, git_config_backend *in);
+
+static void set_parse_error(struct reader *reader, int col, const char *error_str)
+{
+ giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
+ error_str, reader->file_path, reader->line_number, col);
+}
+
+static int config_error_readonly(void)
+{
+ giterr_set(GITERR_CONFIG, "this backend is read-only");
+ return -1;
+}
+
+static void cvar_free(cvar_t *var)
+{
+ if (var == NULL)
+ return;
+
+ git__free((char*)var->entry->name);
+ git__free((char *)var->entry->value);
+ git__free(var->entry);
+ git__free(var);
+}
+
+int git_config_file_normalize_section(char *start, char *end)
+{
+ char *scan;
+
+ if (start == end)
+ return GIT_EINVALIDSPEC;
+
+ /* Validate and downcase range */
+ for (scan = start; *scan; ++scan) {
+ if (end && scan >= end)
+ break;
+ if (isalnum(*scan))
+ *scan = (char)git__tolower(*scan);
+ else if (*scan != '-' || scan == start)
+ return GIT_EINVALIDSPEC;
+ }
+
+ if (scan == start)
+ return GIT_EINVALIDSPEC;
+
+ return 0;
+}
+
+/* Add or append the new config option */
+static int append_entry(git_strmap *values, cvar_t *var)
+{
+ git_strmap_iter pos;
+ cvar_t *existing;
+ int error = 0;
+
+ pos = git_strmap_lookup_index(values, var->entry->name);
+ if (!git_strmap_valid_index(values, pos)) {
+ git_strmap_insert(values, var->entry->name, var, error);
+ } else {
+ existing = git_strmap_value_at(values, pos);
+ while (existing->next != NULL) {
+ existing = existing->next;
+ }
+ existing->next = var;
+ }
+
+ if (error > 0)
+ error = 0;
+
+ return error;
+}
+
+static void free_vars(git_strmap *values)
+{
+ cvar_t *var = NULL;
+
+ if (values == NULL)
+ return;
+
+ git_strmap_foreach_value(values, var,
+ while (var != NULL) {
+ cvar_t *next = CVAR_LIST_NEXT(var);
+ cvar_free(var);
+ var = next;
+ });
+
+ git_strmap_free(values);
+}
+
+static void refcounted_strmap_free(refcounted_strmap *map)
+{
+ if (!map)
+ return;
+
+ if (git_atomic_dec(&map->refcount) != 0)
+ return;
+
+ free_vars(map->values);
+ git__free(map);
+}
+
+/**
+ * Take the current values map from the backend and increase its
+ * refcount. This is its own function to make sure we use the mutex to
+ * avoid the map pointer from changing under us.
+ */
+static refcounted_strmap *refcounted_strmap_take(diskfile_header *h)
+{
+ refcounted_strmap *map;
+
+ if (git_mutex_lock(&h->values_mutex) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock config backend");
+ return NULL;
+ }
+
+ map = h->values;
+ git_atomic_inc(&map->refcount);
+
+ git_mutex_unlock(&h->values_mutex);
+
+ return map;
+}
+
+static int refcounted_strmap_alloc(refcounted_strmap **out)
+{
+ refcounted_strmap *map;
+ int error;
+
+ map = git__calloc(1, sizeof(refcounted_strmap));
+ GITERR_CHECK_ALLOC(map);
+
+ git_atomic_set(&map->refcount, 1);
+
+ if ((error = git_strmap_alloc(&map->values)) < 0)
+ git__free(map);
+ else
+ *out = map;
+
+ return error;
+}
+
+static int config_open(git_config_backend *cfg, git_config_level_t level)
+{
+ int res;
+ struct reader *reader;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+
+ b->level = level;
+
+ if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
+ return res;
+
+ git_array_init(b->readers);
+ reader = git_array_alloc(b->readers);
+ if (!reader) {
+ refcounted_strmap_free(b->header.values);
+ return -1;
+ }
+ memset(reader, 0, sizeof(struct reader));
+
+ reader->file_path = git__strdup(b->file_path);
+ GITERR_CHECK_ALLOC(reader->file_path);
+
+ git_buf_init(&reader->buffer, 0);
+ res = git_futils_readbuffer_updated(
+ &reader->buffer, b->file_path, &reader->checksum, NULL);
+
+ /* It's fine if the file doesn't exist */
+ if (res == GIT_ENOTFOUND)
+ return 0;
+
+ if (res < 0 || (res = config_read(b->header.values->values, b, reader, level, 0)) < 0) {
+ refcounted_strmap_free(b->header.values);
+ b->header.values = NULL;
+ }
+
+ reader = git_array_get(b->readers, 0);
+ git_buf_free(&reader->buffer);
+
+ return res;
+}
+
+/* The meat of the refresh, as we want to use it in different places */
+static int config__refresh(git_config_backend *cfg)
+{
+ refcounted_strmap *values = NULL, *tmp;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ struct reader *reader = NULL;
+ int error = 0;
+
+ if ((error = refcounted_strmap_alloc(&values)) < 0)
+ goto out;
+
+ reader = git_array_get(b->readers, git_array_size(b->readers) - 1);
+ GITERR_CHECK_ALLOC(reader);
+
+ if ((error = config_read(values->values, b, reader, b->level, 0)) < 0)
+ goto out;
+
+ if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock config backend");
+ goto out;
+ }
+
+ tmp = b->header.values;
+ b->header.values = values;
+ values = tmp;
+
+ git_mutex_unlock(&b->header.values_mutex);
+
+out:
+ refcounted_strmap_free(values);
+ if (reader)
+ git_buf_free(&reader->buffer);
+ return error;
+}
+
+static int config_refresh(git_config_backend *cfg)
+{
+ int error = 0, updated = 0, any_updated = 0;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ struct reader *reader = NULL;
+ uint32_t i;
+
+ for (i = 0; i < git_array_size(b->readers); i++) {
+ reader = git_array_get(b->readers, i);
+ error = git_futils_readbuffer_updated(
+ &reader->buffer, reader->file_path,
+ &reader->checksum, &updated);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ if (updated)
+ any_updated = 1;
+ }
+
+ if (!any_updated)
+ return (error == GIT_ENOTFOUND) ? 0 : error;
+
+ return config__refresh(cfg);
+}
+
+static void backend_free(git_config_backend *_backend)
+{
+ diskfile_backend *backend = (diskfile_backend *)_backend;
+ uint32_t i;
+
+ if (backend == NULL)
+ return;
+
+ for (i = 0; i < git_array_size(backend->readers); i++) {
+ struct reader *r = git_array_get(backend->readers, i);
+ git__free(r->file_path);
+ }
+ git_array_clear(backend->readers);
+
+ git__free(backend->file_path);
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
+ git__free(backend);
+}
+
+static void config_iterator_free(
+ git_config_iterator* iter)
+{
+ iter->backend->free(iter->backend);
+ git__free(iter);
+}
+
+static int config_iterator_next(
+ git_config_entry **entry,
+ git_config_iterator *iter)
+{
+ git_config_file_iter *it = (git_config_file_iter *) iter;
+ diskfile_header *h = (diskfile_header *) it->parent.backend;
+ git_strmap *values = h->values->values;
+ int err = 0;
+ cvar_t * var;
+
+ if (it->next_var == NULL) {
+ err = git_strmap_next((void**) &var, &(it->iter), values);
+ } else {
+ var = it->next_var;
+ }
+
+ if (err < 0) {
+ it->next_var = NULL;
+ return err;
+ }
+
+ *entry = var->entry;
+ it->next_var = CVAR_LIST_NEXT(var);
+
+ return 0;
+}
+
+static int config_iterator_new(
+ git_config_iterator **iter,
+ struct git_config_backend* backend)
+{
+ diskfile_header *h;
+ git_config_file_iter *it;
+ git_config_backend *snapshot;
+ diskfile_backend *b = (diskfile_backend *) backend;
+ int error;
+
+ if ((error = config_snapshot(&snapshot, backend)) < 0)
+ return error;
+
+ if ((error = snapshot->open(snapshot, b->level)) < 0)
+ return error;
+
+ it = git__calloc(1, sizeof(git_config_file_iter));
+ GITERR_CHECK_ALLOC(it);
+
+ h = (diskfile_header *)snapshot;
+
+ /* strmap_begin() is currently a macro returning 0 */
+ GIT_UNUSED(h);
+
+ it->parent.backend = snapshot;
+ it->iter = git_strmap_begin(h->values);
+ it->next_var = NULL;
+
+ it->parent.next = config_iterator_next;
+ it->parent.free = config_iterator_free;
+ *iter = (git_config_iterator *) it;
+
+ return 0;
+}
+
+static int config_set(git_config_backend *cfg, const char *name, const char *value)
+{
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ char *key, *esc_value = NULL;
+ khiter_t pos;
+ int rval, ret;
+
+ if ((rval = git_config__normalize_name(name, &key)) < 0)
+ return rval;
+
+ if ((map = refcounted_strmap_take(&b->header)) == NULL)
+ return -1;
+ values = map->values;
+
+ /*
+ * Try to find it in the existing values and update it if it
+ * only has one value.
+ */
+ pos = git_strmap_lookup_index(values, key);
+ if (git_strmap_valid_index(values, pos)) {
+ cvar_t *existing = git_strmap_value_at(values, pos);
+
+ if (existing->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
+ ret = -1;
+ goto out;
+ }
+
+ /* don't update if old and new values already match */
+ if ((!existing->entry->value && !value) ||
+ (existing->entry->value && value &&
+ !strcmp(existing->entry->value, value))) {
+ ret = 0;
+ goto out;
+ }
+ }
+
+ /* No early returns due to sanity checks, let's write it out and refresh */
+
+ if (value) {
+ esc_value = escape_value(value);
+ GITERR_CHECK_ALLOC(esc_value);
+ }
+
+ if ((ret = config_write(b, key, NULL, esc_value)) < 0)
+ goto out;
+
+ ret = config_refresh(cfg);
+
+out:
+ refcounted_strmap_free(map);
+ git__free(esc_value);
+ git__free(key);
+ return ret;
+}
+
+/* release the map containing the entry as an equivalent to freeing it */
+static void release_map(git_config_entry *entry)
+{
+ refcounted_strmap *map = (refcounted_strmap *) entry->payload;
+ refcounted_strmap_free(map);
+}
+
+/*
+ * Internal function that actually gets the value in string form
+ */
+static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out)
+{
+ diskfile_header *h = (diskfile_header *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ khiter_t pos;
+ cvar_t *var;
+ int error = 0;
+
+ if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0))
+ return error;
+
+ if ((map = refcounted_strmap_take(h)) == NULL)
+ return -1;
+ values = map->values;
+
+ pos = git_strmap_lookup_index(values, key);
+
+ /* no error message; the config system will write one */
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ return GIT_ENOTFOUND;
+ }
+
+ var = git_strmap_value_at(values, pos);
+ while (var->next)
+ var = var->next;
+
+ *out = var->entry;
+ (*out)->free = release_map;
+ (*out)->payload = map;
+
+ return error;
+}
+
+static int config_set_multivar(
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
+{
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ regex_t preg;
+ int result;
+
+ assert(regexp);
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ result = p_regcomp(&preg, regexp, REG_EXTENDED);
+ if (result != 0) {
+ giterr_set_regex(&preg, result);
+ result = -1;
+ goto out;
+ }
+
+ /* If we do have it, set call config_write() and reload */
+ if ((result = config_write(b, key, &preg, value)) < 0)
+ goto out;
+
+ result = config_refresh(cfg);
+
+out:
+ git__free(key);
+ regfree(&preg);
+
+ return result;
+}
+
+static int config_delete(git_config_backend *cfg, const char *name)
+{
+ cvar_t *var;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map; git_strmap *values;
+ char *key;
+ int result;
+ khiter_t pos;
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ if ((map = refcounted_strmap_take(&b->header)) == NULL)
+ return -1;
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+ git__free(key);
+
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ return GIT_ENOTFOUND;
+ }
+
+ var = git_strmap_value_at(values, pos);
+ refcounted_strmap_free(map);
+
+ if (var->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
+ return -1;
+ }
+
+ if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0)
+ return result;
+
+ return config_refresh(cfg);
+}
+
+static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ char *key;
+ regex_t preg;
+ int result;
+ khiter_t pos;
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ if ((map = refcounted_strmap_take(&b->header)) == NULL)
+ return -1;
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ git__free(key);
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ return GIT_ENOTFOUND;
+ }
+
+ refcounted_strmap_free(map);
+
+ result = p_regcomp(&preg, regexp, REG_EXTENDED);
+ if (result != 0) {
+ giterr_set_regex(&preg, result);
+ result = -1;
+ goto out;
+ }
+
+ if ((result = config_write(b, key, &preg, NULL)) < 0)
+ goto out;
+
+ result = config_refresh(cfg);
+
+out:
+ git__free(key);
+ regfree(&preg);
+ return result;
+}
+
+static int config_snapshot(git_config_backend **out, git_config_backend *in)
+{
+ diskfile_backend *b = (diskfile_backend *) in;
+
+ return git_config_file__snapshot(out, b);
+}
+
+static int config_lock(git_config_backend *_cfg)
+{
+ diskfile_backend *cfg = (diskfile_backend *) _cfg;
+ int error;
+
+ if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0)
+ return error;
+
+ error = git_futils_readbuffer(&cfg->locked_content, cfg->file_path);
+ if (error < 0 && error != GIT_ENOTFOUND) {
+ git_filebuf_cleanup(&cfg->locked_buf);
+ return error;
+ }
+
+ cfg->locked = true;
+ return 0;
+
+}
+
+static int config_unlock(git_config_backend *_cfg, int success)
+{
+ diskfile_backend *cfg = (diskfile_backend *) _cfg;
+ int error = 0;
+
+ if (success) {
+ git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size);
+ error = git_filebuf_commit(&cfg->locked_buf);
+ }
+
+ git_filebuf_cleanup(&cfg->locked_buf);
+ git_buf_free(&cfg->locked_content);
+ cfg->locked = false;
+
+ return error;
+}
+
+int git_config_file__ondisk(git_config_backend **out, const char *path)
+{
+ diskfile_backend *backend;
+
+ backend = git__calloc(1, sizeof(diskfile_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ git_mutex_init(&backend->header.values_mutex);
+
+ backend->file_path = git__strdup(path);
+ GITERR_CHECK_ALLOC(backend->file_path);
+
+ backend->header.parent.open = config_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set;
+ backend->header.parent.set_multivar = config_set_multivar;
+ backend->header.parent.del = config_delete;
+ backend->header.parent.del_multivar = config_delete_multivar;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.snapshot = config_snapshot;
+ backend->header.parent.lock = config_lock;
+ backend->header.parent.unlock = config_unlock;
+ backend->header.parent.free = backend_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
+
+static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
+
+static int config_set_multivar_readonly(
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
+
+static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+
+ return config_error_readonly();
+}
+
+static int config_delete_readonly(git_config_backend *cfg, const char *name)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+
+ return config_error_readonly();
+}
+
+static int config_lock_readonly(git_config_backend *_cfg)
+{
+ GIT_UNUSED(_cfg);
+
+ return config_error_readonly();
+}
+
+static int config_unlock_readonly(git_config_backend *_cfg, int success)
+{
+ GIT_UNUSED(_cfg);
+ GIT_UNUSED(success);
+
+ return config_error_readonly();
+}
+
+static void backend_readonly_free(git_config_backend *_backend)
+{
+ diskfile_backend *backend = (diskfile_backend *)_backend;
+
+ if (backend == NULL)
+ return;
+
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
+ git__free(backend);
+}
+
+static int config_readonly_open(git_config_backend *cfg, git_config_level_t level)
+{
+ diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
+ diskfile_backend *src = b->snapshot_from;
+ diskfile_header *src_header = &src->header;
+ refcounted_strmap *src_map;
+ int error;
+
+ if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0)
+ return error;
+
+ /* We're just copying data, don't care about the level */
+ GIT_UNUSED(level);
+
+ if ((src_map = refcounted_strmap_take(src_header)) == NULL)
+ return -1;
+ b->header.values = src_map;
+
+ return 0;
+}
+
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
+{
+ diskfile_readonly_backend *backend;
+
+ backend = git__calloc(1, sizeof(diskfile_readonly_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ git_mutex_init(&backend->header.values_mutex);
+
+ backend->snapshot_from = in;
+
+ backend->header.parent.readonly = 1;
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ backend->header.parent.open = config_readonly_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set_readonly;
+ backend->header.parent.set_multivar = config_set_multivar_readonly;
+ backend->header.parent.del = config_delete_readonly;
+ backend->header.parent.del_multivar = config_delete_multivar_readonly;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.lock = config_lock_readonly;
+ backend->header.parent.unlock = config_unlock_readonly;
+ backend->header.parent.free = backend_readonly_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
+
+static int reader_getchar_raw(struct reader *reader)
+{
+ int c;
+
+ c = *reader->read_ptr++;
+
+ /*
+ Win 32 line breaks: if we find a \r\n sequence,
+ return only the \n as a newline
+ */
+ if (c == '\r' && *reader->read_ptr == '\n') {
+ reader->read_ptr++;
+ c = '\n';
+ }
+
+ if (c == '\n')
+ reader->line_number++;
+
+ if (c == 0) {
+ reader->eof = 1;
+ c = '\0';
+ }
+
+ return c;
+}
+
+#define SKIP_WHITESPACE (1 << 1)
+#define SKIP_COMMENTS (1 << 2)
+
+static int reader_getchar(struct reader *reader, int flags)
+{
+ const int skip_whitespace = (flags & SKIP_WHITESPACE);
+ const int skip_comments = (flags & SKIP_COMMENTS);
+ int c;
+
+ assert(reader->read_ptr);
+
+ do {
+ c = reader_getchar_raw(reader);
+ } while (c != '\n' && c != '\0' && skip_whitespace && git__isspace(c));
+
+ if (skip_comments && (c == '#' || c == ';')) {
+ do {
+ c = reader_getchar_raw(reader);
+ } while (c != '\n' && c != '\0');
+ }
+
+ return c;
+}
+
+/*
+ * Read the next char, but don't move the reading pointer.
+ */
+static int reader_peek(struct reader *reader, int flags)
+{
+ void *old_read_ptr;
+ int old_lineno, old_eof;
+ int ret;
+
+ assert(reader->read_ptr);
+
+ old_read_ptr = reader->read_ptr;
+ old_lineno = reader->line_number;
+ old_eof = reader->eof;
+
+ ret = reader_getchar(reader, flags);
+
+ reader->read_ptr = old_read_ptr;
+ reader->line_number = old_lineno;
+ reader->eof = old_eof;
+
+ return ret;
+}
+
+/*
+ * Read and consume a line, returning it in newly-allocated memory.
+ */
+static char *reader_readline(struct reader *reader, bool skip_whitespace)
+{
+ char *line = NULL;
+ char *line_src, *line_end;
+ size_t line_len, alloc_len;
+
+ line_src = reader->read_ptr;
+
+ if (skip_whitespace) {
+ /* Skip empty empty lines */
+ while (git__isspace(*line_src))
+ ++line_src;
+ }
+
+ line_end = strchr(line_src, '\n');
+
+ /* no newline at EOF */
+ if (line_end == NULL)
+ line_end = strchr(line_src, 0);
+
+ line_len = line_end - line_src;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, line_len, 1) ||
+ (line = git__malloc(alloc_len)) == NULL) {
+ return NULL;
+ }
+
+ memcpy(line, line_src, line_len);
+
+ do line[line_len] = '\0';
+ while (line_len-- > 0 && git__isspace(line[line_len]));
+
+ if (*line_end == '\n')
+ line_end++;
+
+ if (*line_end == '\0')
+ reader->eof = 1;
+
+ reader->line_number++;
+ reader->read_ptr = line_end;
+
+ return line;
+}
+
+/*
+ * Consume a line, without storing it anywhere
+ */
+static void reader_consume_line(struct reader *reader)
+{
+ char *line_start, *line_end;
+
+ line_start = reader->read_ptr;
+ line_end = strchr(line_start, '\n');
+ /* No newline at EOF */
+ if(line_end == NULL){
+ line_end = strchr(line_start, '\0');
+ }
+
+ if (*line_end == '\n')
+ line_end++;
+
+ if (*line_end == '\0')
+ reader->eof = 1;
+
+ reader->line_number++;
+ reader->read_ptr = line_end;
+}
+
+GIT_INLINE(int) config_keychar(int c)
+{
+ return isalnum(c) || c == '-';
+}
+
+static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
+{
+ int c, rpos;
+ char *first_quote, *last_quote;
+ git_buf buf = GIT_BUF_INIT;
+ size_t quoted_len, alloc_len, base_name_len = strlen(base_name);
+
+ /*
+ * base_name is what came before the space. We should be at the
+ * first quotation mark, except for now, line isn't being kept in
+ * sync so we only really use it to calculate the length.
+ */
+
+ first_quote = strchr(line, '"');
+ if (first_quote == NULL) {
+ set_parse_error(reader, 0, "Missing quotation marks in section header");
+ return -1;
+ }
+
+ last_quote = strrchr(line, '"');
+ quoted_len = last_quote - first_quote;
+
+ if (quoted_len == 0) {
+ set_parse_error(reader, 0, "Missing closing quotation mark in section header");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
+
+ git_buf_grow(&buf, alloc_len);
+ git_buf_printf(&buf, "%s.", base_name);
+
+ rpos = 0;
+
+ line = first_quote;
+ c = line[++rpos];
+
+ /*
+ * At the end of each iteration, whatever is stored in c will be
+ * added to the string. In case of error, jump to out
+ */
+ do {
+
+ switch (c) {
+ case 0:
+ set_parse_error(reader, 0, "Unexpected end-of-line in section header");
+ git_buf_free(&buf);
+ return -1;
+
+ case '"':
+ goto end_parse;
+
+ case '\\':
+ c = line[++rpos];
+
+ if (c == 0) {
+ set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
+ git_buf_free(&buf);
+ return -1;
+ }
+
+ default:
+ break;
+ }
+
+ git_buf_putc(&buf, (char)c);
+ c = line[++rpos];
+ } while (line + rpos < last_quote);
+
+end_parse:
+ if (line[rpos] != '"' || line[rpos + 1] != ']') {
+ set_parse_error(reader, rpos, "Unexpected text after closing quotes");
+ git_buf_free(&buf);
+ return -1;
+ }
+
+ *section_name = git_buf_detach(&buf);
+ return 0;
+}
+
+static int parse_section_header(struct reader *reader, char **section_out)
+{
+ char *name, *name_end;
+ int name_length, c, pos;
+ int result;
+ char *line;
+ size_t line_len;
+
+ line = reader_readline(reader, true);
+ if (line == NULL)
+ return -1;
+
+ /* find the end of the variable's name */
+ name_end = strrchr(line, ']');
+ if (name_end == NULL) {
+ git__free(line);
+ set_parse_error(reader, 0, "Missing ']' in section header");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
+ name = git__malloc(line_len);
+ GITERR_CHECK_ALLOC(name);
+
+ name_length = 0;
+ pos = 0;
+
+ /* Make sure we were given a section header */
+ c = line[pos++];
+ assert(c == '[');
+
+ c = line[pos++];
+
+ do {
+ if (git__isspace(c)){
+ name[name_length] = '\0';
+ result = parse_section_header_ext(reader, line, name, section_out);
+ git__free(line);
+ git__free(name);
+ return result;
+ }
+
+ if (!config_keychar(c) && c != '.') {
+ set_parse_error(reader, pos, "Unexpected character in header");
+ goto fail_parse;
+ }
+
+ name[name_length++] = (char)git__tolower(c);
+
+ } while ((c = line[pos++]) != ']');
+
+ if (line[pos - 1] != ']') {
+ set_parse_error(reader, pos, "Unexpected end of file");
+ goto fail_parse;
+ }
+
+ git__free(line);
+
+ name[name_length] = 0;
+ *section_out = name;
+
+ return 0;
+
+fail_parse:
+ git__free(line);
+ git__free(name);
+ return -1;
+}
+
+static int skip_bom(struct reader *reader)
+{
+ git_bom_t bom;
+ int bom_offset = git_buf_text_detect_bom(&bom,
+ &reader->buffer, reader->read_ptr - reader->buffer.ptr);
+
+ if (bom == GIT_BOM_UTF8)
+ reader->read_ptr += bom_offset;
+
+ /* TODO: reference implementation is pretty stupid with BoM */
+
+ return 0;
+}
+
+/*
+ (* basic types *)
+ digit = "0".."9"
+ integer = digit { digit }
+ alphabet = "a".."z" + "A" .. "Z"
+
+ section_char = alphabet | "." | "-"
+ extension_char = (* any character except newline *)
+ any_char = (* any character *)
+ variable_char = "alphabet" | "-"
+
+
+ (* actual grammar *)
+ config = { section }
+
+ section = header { definition }
+
+ header = "[" section [subsection | subsection_ext] "]"
+
+ subsection = "." section
+ subsection_ext = "\"" extension "\""
+
+ section = section_char { section_char }
+ extension = extension_char { extension_char }
+
+ definition = variable_name ["=" variable_value] "\n"
+
+ variable_name = variable_char { variable_char }
+ variable_value = string | boolean | integer
+
+ string = quoted_string | plain_string
+ quoted_string = "\"" plain_string "\""
+ plain_string = { any_char }
+
+ boolean = boolean_true | boolean_false
+ boolean_true = "yes" | "1" | "true" | "on"
+ boolean_false = "no" | "0" | "false" | "off"
+*/
+
+static int strip_comments(char *line, int in_quotes)
+{
+ int quote_count = in_quotes, backslash_count = 0;
+ char *ptr;
+
+ for (ptr = line; *ptr; ++ptr) {
+ if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
+ quote_count++;
+
+ if ((ptr[0] == ';' || ptr[0] == '#') &&
+ (quote_count % 2) == 0 &&
+ (backslash_count % 2) == 0) {
+ ptr[0] = '\0';
+ break;
+ }
+
+ if (ptr[0] == '\\')
+ backslash_count++;
+ else
+ backslash_count = 0;
+ }
+
+ /* skip any space at the end */
+ while (ptr > line && git__isspace(ptr[-1])) {
+ ptr--;
+ }
+ ptr[0] = '\0';
+
+ return quote_count;
+}
+
+static int included_path(git_buf *out, const char *dir, const char *path)
+{
+ /* From the user's home */
+ if (path[0] == '~' && path[1] == '/')
+ return git_sysdir_find_global_file(out, &path[1]);
+
+ return git_path_join_unrooted(out, path, dir, NULL);
+}
+
+static const char *escapes = "ntb\"\\";
+static const char *escaped = "\n\t\b\"\\";
+
+/* Escape the values to write them to the file */
+static char *escape_value(const char *ptr)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t len;
+ const char *esc;
+
+ assert(ptr);
+
+ len = strlen(ptr);
+ if (!len)
+ return git__calloc(1, sizeof(char));
+
+ git_buf_grow(&buf, len);
+
+ while (*ptr != '\0') {
+ if ((esc = strchr(escaped, *ptr)) != NULL) {
+ git_buf_putc(&buf, '\\');
+ git_buf_putc(&buf, escapes[esc - escaped]);
+ } else {
+ git_buf_putc(&buf, *ptr);
+ }
+ ptr++;
+ }
+
+ if (git_buf_oom(&buf)) {
+ git_buf_free(&buf);
+ return NULL;
+ }
+
+ return git_buf_detach(&buf);
+}
+
+/* '\"' -> '"' etc */
+static int unescape_line(
+ char **out, bool *is_multi, const char *ptr, int quote_count)
+{
+ char *str, *fixed, *esc;
+ size_t ptr_len = strlen(ptr), alloc_len;
+
+ *is_multi = false;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) ||
+ (str = git__malloc(alloc_len)) == NULL) {
+ return -1;
+ }
+
+ fixed = str;
+
+ while (*ptr != '\0') {
+ if (*ptr == '"') {
+ quote_count++;
+ } else if (*ptr != '\\') {
+ *fixed++ = *ptr;
+ } else {
+ /* backslash, check the next char */
+ ptr++;
+ /* if we're at the end, it's a multiline, so keep the backslash */
+ if (*ptr == '\0') {
+ *is_multi = true;
+ goto done;
+ }
+ if ((esc = strchr(escapes, *ptr)) != NULL) {
+ *fixed++ = escaped[esc - escapes];
+ } else {
+ git__free(str);
+ giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
+ return -1;
+ }
+ }
+ ptr++;
+ }
+
+done:
+ *fixed = '\0';
+ *out = str;
+
+ return 0;
+}
+
+static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
+{
+ char *line = NULL, *proc_line = NULL;
+ int quote_count;
+ bool multiline;
+
+ /* Check that the next line exists */
+ line = reader_readline(reader, false);
+ if (line == NULL)
+ return -1;
+
+ /* We've reached the end of the file, there is no continuation.
+ * (this is not an error).
+ */
+ if (line[0] == '\0') {
+ git__free(line);
+ return 0;
+ }
+
+ quote_count = strip_comments(line, !!in_quotes);
+
+ /* If it was just a comment, pretend it didn't exist */
+ if (line[0] == '\0') {
+ git__free(line);
+ return parse_multiline_variable(reader, value, quote_count);
+ /* TODO: unbounded recursion. This **could** be exploitable */
+ }
+
+ if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) {
+ git__free(line);
+ return -1;
+ }
+ /* add this line to the multiline var */
+
+ git_buf_puts(value, proc_line);
+ git__free(line);
+ git__free(proc_line);
+
+ /*
+ * If we need to continue reading the next line, let's just
+ * keep putting stuff in the buffer
+ */
+ if (multiline)
+ return parse_multiline_variable(reader, value, quote_count);
+
+ return 0;
+}
+
+GIT_INLINE(bool) is_namechar(char c)
+{
+ return isalnum(c) || c == '-';
+}
+
+static int parse_name(
+ char **name, const char **value, struct reader *reader, const char *line)
+{
+ const char *name_end = line, *value_start;
+
+ *name = NULL;
+ *value = NULL;
+
+ while (*name_end && is_namechar(*name_end))
+ name_end++;
+
+ if (line == name_end) {
+ set_parse_error(reader, 0, "Invalid configuration key");
+ return -1;
+ }
+
+ value_start = name_end;
+
+ while (*value_start && git__isspace(*value_start))
+ value_start++;
+
+ if (*value_start == '=') {
+ *value = value_start + 1;
+ } else if (*value_start) {
+ set_parse_error(reader, 0, "Invalid configuration key");
+ return -1;
+ }
+
+ if ((*name = git__strndup(line, name_end - line)) == NULL)
+ return -1;
+
+ return 0;
+}
+
+static int parse_variable(struct reader *reader, char **var_name, char **var_value)
+{
+ const char *value_start = NULL;
+ char *line;
+ int quote_count;
+ bool multiline;
+
+ line = reader_readline(reader, true);
+ if (line == NULL)
+ return -1;
+
+ quote_count = strip_comments(line, 0);
+
+ /* If there is no value, boolean true is assumed */
+ *var_value = NULL;
+
+ if (parse_name(var_name, &value_start, reader, line) < 0)
+ goto on_error;
+
+ /*
+ * Now, let's try to parse the value
+ */
+ if (value_start != NULL) {
+ while (git__isspace(value_start[0]))
+ value_start++;
+
+ if (unescape_line(var_value, &multiline, value_start, 0) < 0)
+ goto on_error;
+
+ if (multiline) {
+ git_buf multi_value = GIT_BUF_INIT;
+ git_buf_attach(&multi_value, *var_value, 0);
+
+ if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 ||
+ git_buf_oom(&multi_value)) {
+ git_buf_free(&multi_value);
+ goto on_error;
+ }
+
+ *var_value = git_buf_detach(&multi_value);
+ }
+ }
+
+ git__free(line);
+ return 0;
+
+on_error:
+ git__free(*var_name);
+ git__free(line);
+ return -1;
+}
+
+static int config_parse(
+ struct reader *reader,
+ int (*on_section)(struct reader **reader, const char *current_section, const char *line, size_t line_len, void *data),
+ int (*on_variable)(struct reader **reader, const char *current_section, char *var_name, char *var_value, const char *line, size_t line_len, void *data),
+ int (*on_comment)(struct reader **reader, const char *line, size_t line_len, void *data),
+ int (*on_eof)(struct reader **reader, const char *current_section, void *data),
+ void *data)
+{
+ char *current_section = NULL, *var_name, *var_value, *line_start;
+ char c;
+ size_t line_len;
+ int result = 0;
+
+ skip_bom(reader);
+
+ while (result == 0 && !reader->eof) {
+ line_start = reader->read_ptr;
+
+ c = reader_peek(reader, SKIP_WHITESPACE);
+
+ switch (c) {
+ case '\0': /* EOF when peeking, set EOF in the reader to exit the loop */
+ reader->eof = 1;
+ break;
+
+ case '[': /* section header, new section begins */
+ git__free(current_section);
+ current_section = NULL;
+
+ if ((result = parse_section_header(reader, ¤t_section)) == 0 && on_section) {
+ line_len = reader->read_ptr - line_start;
+ result = on_section(&reader, current_section, line_start, line_len, data);
+ }
+ break;
+
+ case '\n': /* comment or whitespace-only */
+ case ';':
+ case '#':
+ reader_consume_line(reader);
+
+ if (on_comment) {
+ line_len = reader->read_ptr - line_start;
+ result = on_comment(&reader, line_start, line_len, data);
+ }
+ break;
+
+ default: /* assume variable declaration */
+ if ((result = parse_variable(reader, &var_name, &var_value)) == 0 && on_variable) {
+ line_len = reader->read_ptr - line_start;
+ result = on_variable(&reader, current_section, var_name, var_value, line_start, line_len, data);
+ }
+ break;
+ }
+ }
+
+ if (on_eof)
+ result = on_eof(&reader, current_section, data);
+
+ git__free(current_section);
+ return result;
+}
+
+struct parse_data {
+ git_strmap *values;
+ diskfile_backend *cfg_file;
+ uint32_t reader_idx;
+ git_config_level_t level;
+ int depth;
+};
+
+static int read_on_variable(
+ struct reader **reader,
+ const char *current_section,
+ char *var_name,
+ char *var_value,
+ const char *line,
+ size_t line_len,
+ void *data)
+{
+ struct parse_data *parse_data = (struct parse_data *)data;
+ git_buf buf = GIT_BUF_INIT;
+ cvar_t *var;
+ int result = 0;
+
+ GIT_UNUSED(line);
+ GIT_UNUSED(line_len);
+
+ git__strtolower(var_name);
+ git_buf_printf(&buf, "%s.%s", current_section, var_name);
+ git__free(var_name);
+
+ if (git_buf_oom(&buf)) {
+ git__free(var_value);
+ return -1;
+ }
+
+ var = git__calloc(1, sizeof(cvar_t));
+ GITERR_CHECK_ALLOC(var);
+ var->entry = git__calloc(1, sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(var->entry);
+
+ var->entry->name = git_buf_detach(&buf);
+ var->entry->value = var_value;
+ var->entry->level = parse_data->level;
+ var->included = !!parse_data->depth;
+
+ if ((result = append_entry(parse_data->values, var)) < 0)
+ return result;
+
+ result = 0;
+
+ /* Add or append the new config option */
+ if (!git__strcmp(var->entry->name, "include.path")) {
+ struct reader *r;
+ git_buf path = GIT_BUF_INIT;
+ char *dir;
+ uint32_t index;
+
+ r = git_array_alloc(parse_data->cfg_file->readers);
+ /* The reader may have been reallocated */
+ *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
+ memset(r, 0, sizeof(struct reader));
+
+ if ((result = git_path_dirname_r(&path, (*reader)->file_path)) < 0)
+ return result;
+
+ /* We need to know our index in the array, as the next config_parse call may realloc */
+ index = git_array_size(parse_data->cfg_file->readers) - 1;
+ dir = git_buf_detach(&path);
+ result = included_path(&path, dir, var->entry->value);
+ git__free(dir);
+
+ if (result < 0)
+ return result;
+
+ r->file_path = git_buf_detach(&path);
+ git_buf_init(&r->buffer, 0);
+
+ result = git_futils_readbuffer_updated(
+ &r->buffer, r->file_path, &r->checksum, NULL);
+
+ if (result == 0) {
+ result = config_read(parse_data->values, parse_data->cfg_file, r, parse_data->level, parse_data->depth+1);
+ r = git_array_get(parse_data->cfg_file->readers, index);
+ *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
+ } else if (result == GIT_ENOTFOUND) {
+ giterr_clear();
+ result = 0;
+ }
+
+ git_buf_free(&r->buffer);
+ }
+
+ return result;
+}
+
+static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
+{
+ struct parse_data parse_data;
+
+ if (depth >= MAX_INCLUDE_DEPTH) {
+ giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
+ return -1;
+ }
+
+ /* Initialize the reading position */
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
+
+ /* If the file is empty, there's nothing for us to do */
+ if (*reader->read_ptr == '\0')
+ return 0;
+
+ parse_data.values = values;
+ parse_data.cfg_file = cfg_file;
+ parse_data.reader_idx = git_array_size(cfg_file->readers) - 1;
+ parse_data.level = level;
+ parse_data.depth = depth;
+
+ return config_parse(reader, NULL, read_on_variable, NULL, NULL, &parse_data);
+}
+
+static int write_section(git_buf *fbuf, const char *key)
+{
+ int result;
+ const char *dot;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* All of this just for [section "subsection"] */
+ dot = strchr(key, '.');
+ git_buf_putc(&buf, '[');
+ if (dot == NULL) {
+ git_buf_puts(&buf, key);
+ } else {
+ char *escaped;
+ git_buf_put(&buf, key, dot - key);
+ escaped = escape_value(dot + 1);
+ GITERR_CHECK_ALLOC(escaped);
+ git_buf_printf(&buf, " \"%s\"", escaped);
+ git__free(escaped);
+ }
+ git_buf_puts(&buf, "]\n");
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ result = git_buf_put(fbuf, git_buf_cstr(&buf), buf.size);
+ git_buf_free(&buf);
+
+ return result;
+}
+
+static const char *quotes_for_value(const char *value)
+{
+ const char *ptr;
+
+ if (value[0] == ' ' || value[0] == '\0')
+ return "\"";
+
+ for (ptr = value; *ptr; ++ptr) {
+ if (*ptr == ';' || *ptr == '#')
+ return "\"";
+ }
+
+ if (ptr[-1] == ' ')
+ return "\"";
+
+ return "";
+}
+
+struct write_data {
+ git_buf *buf;
+ git_buf buffered_comment;
+ unsigned int in_section : 1,
+ preg_replaced : 1;
+ const char *section;
+ const char *name;
+ const regex_t *preg;
+ const char *value;
+};
+
+static int write_line_to(git_buf *buf, const char *line, size_t line_len)
+{
+ int result = git_buf_put(buf, line, line_len);
+
+ if (!result && line_len && line[line_len-1] != '\n')
+ result = git_buf_printf(buf, "\n");
+
+ return result;
+}
+
+static int write_line(struct write_data *write_data, const char *line, size_t line_len)
+{
+ return write_line_to(write_data->buf, line, line_len);
+}
+
+static int write_value(struct write_data *write_data)
+{
+ const char *q;
+ int result;
+
+ q = quotes_for_value(write_data->value);
+ result = git_buf_printf(write_data->buf,
+ "\t%s = %s%s%s\n", write_data->name, q, write_data->value, q);
+
+ /* If we are updating a single name/value, we're done. Setting `value`
+ * to `NULL` will prevent us from trying to write it again later (in
+ * `write_on_section`) if we see the same section repeated.
+ */
+ if (!write_data->preg)
+ write_data->value = NULL;
+
+ return result;
+}
+
+static int write_on_section(
+ struct reader **reader,
+ const char *current_section,
+ const char *line,
+ size_t line_len,
+ void *data)
+{
+ struct write_data *write_data = (struct write_data *)data;
+ int result = 0;
+
+ GIT_UNUSED(reader);
+
+ /* If we were previously in the correct section (but aren't anymore)
+ * and haven't written our value (for a simple name/value set, not
+ * a multivar), then append it to the end of the section before writing
+ * the new one.
+ */
+ if (write_data->in_section && !write_data->preg && write_data->value)
+ result = write_value(write_data);
+
+ write_data->in_section = strcmp(current_section, write_data->section) == 0;
+
+ /*
+ * If there were comments just before this section, dump them as well.
+ */
+ if (!result) {
+ result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size);
+ git_buf_clear(&write_data->buffered_comment);
+ }
+
+ if (!result)
+ result = write_line(write_data, line, line_len);
+
+ return result;
+}
+
+static int write_on_variable(
+ struct reader **reader,
+ const char *current_section,
+ char *var_name,
+ char *var_value,
+ const char *line,
+ size_t line_len,
+ void *data)
+{
+ struct write_data *write_data = (struct write_data *)data;
+ bool has_matched = false;
+ int error;
+
+ GIT_UNUSED(reader);
+ GIT_UNUSED(current_section);
+
+ /*
+ * If there were comments just before this variable, let's dump them as well.
+ */
+ if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
+ return error;
+
+ git_buf_clear(&write_data->buffered_comment);
+
+ /* See if we are to update this name/value pair; first examine name */
+ if (write_data->in_section &&
+ strcasecmp(write_data->name, var_name) == 0)
+ has_matched = true;
+
+ /* If we have a regex to match the value, see if it matches */
+ if (has_matched && write_data->preg != NULL)
+ has_matched = (regexec(write_data->preg, var_value, 0, NULL, 0) == 0);
+
+ git__free(var_name);
+ git__free(var_value);
+
+ /* If this isn't the name/value we're looking for, simply dump the
+ * existing data back out and continue on.
+ */
+ if (!has_matched)
+ return write_line(write_data, line, line_len);
+
+ write_data->preg_replaced = 1;
+
+ /* If value is NULL, we are deleting this value; write nothing. */
+ if (!write_data->value)
+ return 0;
+
+ return write_value(write_data);
+}
+
+static int write_on_comment(struct reader **reader, const char *line, size_t line_len, void *data)
+{
+ struct write_data *write_data;
+
+ GIT_UNUSED(reader);
+
+ write_data = (struct write_data *)data;
+ return write_line_to(&write_data->buffered_comment, line, line_len);
+}
+
+static int write_on_eof(
+ struct reader **reader, const char *current_section, void *data)
+{
+ struct write_data *write_data = (struct write_data *)data;
+ int result = 0;
+
+ GIT_UNUSED(reader);
+
+ /*
+ * If we've buffered comments when reaching EOF, make sure to dump them.
+ */
+ if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
+ return result;
+
+ /* If we are at the EOF and have not written our value (again, for a
+ * simple name/value set, not a multivar) then we have never seen the
+ * section in question and should create a new section and write the
+ * value.
+ */
+ if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) {
+ /* write the section header unless we're already in it */
+ if (!current_section || strcmp(current_section, write_data->section))
+ result = write_section(write_data->buf, write_data->section);
+
+ if (!result)
+ result = write_value(write_data);
+ }
+
+ return result;
+}
+
+/*
+ * This is pretty much the parsing, except we write out anything we don't have
+ */
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
+{
+ int result;
+ char *section, *name, *ldot;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf buf = GIT_BUF_INIT;
+ struct reader *reader = git_array_get(cfg->readers, 0);
+ struct write_data write_data;
+
+ if (cfg->locked) {
+ result = git_buf_puts(&reader->buffer, git_buf_cstr(&cfg->locked_content));
+ } else {
+ /* Lock the file */
+ if ((result = git_filebuf_open(
+ &file, cfg->file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) {
+ git_buf_free(&reader->buffer);
+ return result;
+ }
+
+ /* We need to read in our own config file */
+ result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
+ }
+
+ /* Initialise the reading position */
+ if (result == GIT_ENOTFOUND) {
+ reader->read_ptr = NULL;
+ reader->eof = 1;
+ git_buf_clear(&reader->buffer);
+ } else if (result == 0) {
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
+ } else {
+ git_filebuf_cleanup(&file);
+ return -1; /* OS error when reading the file */
+ }
+
+ ldot = strrchr(key, '.');
+ name = ldot + 1;
+ section = git__strndup(key, ldot - key);
+
+ write_data.buf = &buf;
+ git_buf_init(&write_data.buffered_comment, 0);
+ write_data.section = section;
+ write_data.in_section = 0;
+ write_data.preg_replaced = 0;
+ write_data.name = name;
+ write_data.preg = preg;
+ write_data.value = value;
+
+ result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data);
+ git__free(section);
+ git_buf_free(&write_data.buffered_comment);
+
+ if (result < 0) {
+ git_filebuf_cleanup(&file);
+ goto done;
+ }
+
+ if (cfg->locked) {
+ size_t len = buf.asize;
+ /* Update our copy with the modified contents */
+ git_buf_free(&cfg->locked_content);
+ git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len);
+ } else {
+ git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf));
+ result = git_filebuf_commit(&file);
+ }
+
+done:
+ git_buf_free(&buf);
+ git_buf_free(&reader->buffer);
+ return result;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_config_file_h__
+#define INCLUDE_config_file_h__
+
+#include "git2/config.h"
+
+GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level)
+{
+ return cfg->open(cfg, level);
+}
+
+GIT_INLINE(void) git_config_file_free(git_config_backend *cfg)
+{
+ if (cfg)
+ cfg->free(cfg);
+}
+
+GIT_INLINE(int) git_config_file_get_string(
+ git_config_entry **out, git_config_backend *cfg, const char *name)
+{
+ return cfg->get(cfg, name, out);
+}
+
+GIT_INLINE(int) git_config_file_set_string(
+ git_config_backend *cfg, const char *name, const char *value)
+{
+ return cfg->set(cfg, name, value);
+}
+
+GIT_INLINE(int) git_config_file_delete(
+ git_config_backend *cfg, const char *name)
+{
+ return cfg->del(cfg, name);
+}
+
+GIT_INLINE(int) git_config_file_foreach(
+ git_config_backend *cfg,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return git_config_backend_foreach_match(cfg, NULL, fn, data);
+}
+
+GIT_INLINE(int) git_config_file_foreach_match(
+ git_config_backend *cfg,
+ const char *regexp,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return git_config_backend_foreach_match(cfg, regexp, fn, data);
+}
+
+GIT_INLINE(int) git_config_file_lock(git_config_backend *cfg)
+{
+ return cfg->lock(cfg);
+}
+
+GIT_INLINE(int) git_config_file_unlock(git_config_backend *cfg, int success)
+{
+ return cfg->unlock(cfg, success);
+}
+
+extern int git_config_file_normalize_section(char *start, char *end);
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/attr.h"
+#include "git2/blob.h"
+#include "git2/index.h"
+#include "git2/sys/filter.h"
+
+#include "common.h"
+#include "fileops.h"
+#include "hash.h"
+#include "filter.h"
+#include "buf_text.h"
+#include "repository.h"
+
+struct crlf_attrs {
+ int crlf_action;
+ int eol;
+ int auto_crlf;
+ int safe_crlf;
+};
+
+struct crlf_filter {
+ git_filter f;
+};
+
+static int check_crlf(const char *value)
+{
+ if (GIT_ATTR_TRUE(value))
+ return GIT_CRLF_TEXT;
+
+ if (GIT_ATTR_FALSE(value))
+ return GIT_CRLF_BINARY;
+
+ if (GIT_ATTR_UNSPECIFIED(value))
+ return GIT_CRLF_GUESS;
+
+ if (strcmp(value, "input") == 0)
+ return GIT_CRLF_INPUT;
+
+ if (strcmp(value, "auto") == 0)
+ return GIT_CRLF_AUTO;
+
+ return GIT_CRLF_GUESS;
+}
+
+static int check_eol(const char *value)
+{
+ if (GIT_ATTR_UNSPECIFIED(value))
+ return GIT_EOL_UNSET;
+
+ if (strcmp(value, "lf") == 0)
+ return GIT_EOL_LF;
+
+ if (strcmp(value, "crlf") == 0)
+ return GIT_EOL_CRLF;
+
+ return GIT_EOL_UNSET;
+}
+
+static int crlf_input_action(struct crlf_attrs *ca)
+{
+ if (ca->crlf_action == GIT_CRLF_BINARY)
+ return GIT_CRLF_BINARY;
+
+ if (ca->eol == GIT_EOL_LF)
+ return GIT_CRLF_INPUT;
+
+ if (ca->eol == GIT_EOL_CRLF)
+ return GIT_CRLF_CRLF;
+
+ return ca->crlf_action;
+}
+
+static int has_cr_in_index(const git_filter_source *src)
+{
+ git_repository *repo = git_filter_source_repo(src);
+ const char *path = git_filter_source_path(src);
+ git_index *index;
+ const git_index_entry *entry;
+ git_blob *blob;
+ const void *blobcontent;
+ git_off_t blobsize;
+ bool found_cr;
+
+ if (!path)
+ return false;
+
+ if (git_repository_index__weakptr(&index, repo) < 0) {
+ giterr_clear();
+ return false;
+ }
+
+ if (!(entry = git_index_get_bypath(index, path, 0)) &&
+ !(entry = git_index_get_bypath(index, path, 1)))
+ return false;
+
+ if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
+ return true;
+
+ if (git_blob_lookup(&blob, repo, &entry->id) < 0)
+ return false;
+
+ blobcontent = git_blob_rawcontent(blob);
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize))
+ blobsize = (size_t)-1;
+
+ found_cr = (blobcontent != NULL &&
+ blobsize > 0 &&
+ memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
+
+ git_blob_free(blob);
+ return found_cr;
+}
+
+static int crlf_apply_to_odb(
+ struct crlf_attrs *ca,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ /* Empty file? Nothing to do */
+ if (!git_buf_len(from))
+ return 0;
+
+ /* Heuristics to see if we can skip the conversion.
+ * Straight from Core Git.
+ */
+ if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) {
+ git_buf_text_stats stats;
+
+ /* Check heuristics for binary vs text - returns true if binary */
+ if (git_buf_text_gather_stats(&stats, from, false))
+ return GIT_PASSTHROUGH;
+
+ /* If there are no CR characters to filter out, then just pass */
+ if (!stats.cr)
+ return GIT_PASSTHROUGH;
+
+ /* If safecrlf is enabled, sanity-check the result. */
+ if (stats.cr != stats.crlf || stats.lf != stats.crlf) {
+ switch (ca->safe_crlf) {
+ case GIT_SAFE_CRLF_FAIL:
+ giterr_set(
+ GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
+ git_filter_source_path(src));
+ return -1;
+ case GIT_SAFE_CRLF_WARN:
+ /* TODO: issue warning when warning API is available */;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * We're currently not going to even try to convert stuff
+ * that has bare CR characters. Does anybody do that crazy
+ * stuff?
+ */
+ if (stats.cr != stats.crlf)
+ return GIT_PASSTHROUGH;
+
+ if (ca->crlf_action == GIT_CRLF_GUESS) {
+ /*
+ * If the file in the index has any CR in it, do not convert.
+ * This is the new safer autocrlf handling.
+ */
+ if (has_cr_in_index(src))
+ return GIT_PASSTHROUGH;
+ }
+
+ if (!stats.cr)
+ return GIT_PASSTHROUGH;
+ }
+
+ /* Actually drop the carriage returns */
+ return git_buf_text_crlf_to_lf(to, from);
+}
+
+static const char *line_ending(struct crlf_attrs *ca)
+{
+ switch (ca->crlf_action) {
+ case GIT_CRLF_BINARY:
+ case GIT_CRLF_INPUT:
+ return "\n";
+
+ case GIT_CRLF_CRLF:
+ return "\r\n";
+
+ case GIT_CRLF_GUESS:
+ if (ca->auto_crlf == GIT_AUTO_CRLF_FALSE)
+ return "\n";
+ break;
+
+ case GIT_CRLF_AUTO:
+ case GIT_CRLF_TEXT:
+ break;
+
+ default:
+ goto line_ending_error;
+ }
+
+ if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
+ return "\r\n";
+ else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
+ return "\n";
+ else if (ca->eol == GIT_EOL_UNSET)
+ return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n";
+ else if (ca->eol == GIT_EOL_LF)
+ return "\n";
+ else if (ca->eol == GIT_EOL_CRLF)
+ return "\r\n";
+
+line_ending_error:
+ giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
+ return NULL;
+}
+
+static int crlf_apply_to_workdir(
+ struct crlf_attrs *ca, git_buf *to, const git_buf *from)
+{
+ git_buf_text_stats stats;
+ const char *workdir_ending = NULL;
+ bool is_binary;
+
+ /* Empty file? Nothing to do. */
+ if (git_buf_len(from) == 0)
+ return 0;
+
+ /* Determine proper line ending */
+ workdir_ending = line_ending(ca);
+ if (!workdir_ending)
+ return -1;
+
+ /* only LF->CRLF conversion is supported, do nothing on LF platforms */
+ if (strcmp(workdir_ending, "\r\n") != 0)
+ return GIT_PASSTHROUGH;
+
+ /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */
+ is_binary = git_buf_text_gather_stats(&stats, from, false);
+
+ if (stats.lf == 0 || stats.lf == stats.crlf)
+ return GIT_PASSTHROUGH;
+
+ if (ca->crlf_action == GIT_CRLF_AUTO ||
+ ca->crlf_action == GIT_CRLF_GUESS) {
+
+ /* If we have any existing CR or CRLF line endings, do nothing */
+ if (ca->crlf_action == GIT_CRLF_GUESS &&
+ stats.cr > 0 && stats.crlf > 0)
+ return GIT_PASSTHROUGH;
+
+ /* If we have bare CR characters, do nothing */
+ if (stats.cr != stats.crlf)
+ return GIT_PASSTHROUGH;
+
+ /* Don't filter binary files */
+ if (is_binary)
+ return GIT_PASSTHROUGH;
+ }
+
+ return git_buf_text_lf_to_crlf(to, from);
+}
+
+static int crlf_check(
+ git_filter *self,
+ void **payload, /* points to NULL ptr on entry, may be set */
+ const git_filter_source *src,
+ const char **attr_values)
+{
+ int error;
+ struct crlf_attrs ca;
+
+ GIT_UNUSED(self);
+
+ if (!attr_values) {
+ ca.crlf_action = GIT_CRLF_GUESS;
+ ca.eol = GIT_EOL_UNSET;
+ } else {
+ ca.crlf_action = check_crlf(attr_values[2]); /* text */
+ if (ca.crlf_action == GIT_CRLF_GUESS)
+ ca.crlf_action = check_crlf(attr_values[0]); /* clrf */
+ ca.eol = check_eol(attr_values[1]); /* eol */
+ }
+ ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
+ ca.safe_crlf = GIT_SAFE_CRLF_DEFAULT;
+
+ /*
+ * Use the core Git logic to see if we should perform CRLF for this file
+ * based on its attributes & the value of `core.autocrlf`
+ */
+ ca.crlf_action = crlf_input_action(&ca);
+
+ if (ca.crlf_action == GIT_CRLF_BINARY)
+ return GIT_PASSTHROUGH;
+
+ if (ca.crlf_action == GIT_CRLF_GUESS ||
+ ((ca.crlf_action == GIT_CRLF_AUTO || ca.crlf_action == GIT_CRLF_TEXT) &&
+ git_filter_source_mode(src) == GIT_FILTER_SMUDGE)) {
+
+ error = git_repository__cvar(
+ &ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
+ if (error < 0)
+ return error;
+
+ if (ca.crlf_action == GIT_CRLF_GUESS &&
+ ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
+ return GIT_PASSTHROUGH;
+
+ if (ca.auto_crlf == GIT_AUTO_CRLF_INPUT &&
+ git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return GIT_PASSTHROUGH;
+ }
+
+ if (git_filter_source_mode(src) == GIT_FILTER_CLEAN) {
+ error = git_repository__cvar(
+ &ca.safe_crlf, git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF);
+ if (error < 0)
+ return error;
+
+ /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
+ if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) &&
+ ca.safe_crlf == GIT_SAFE_CRLF_FAIL)
+ ca.safe_crlf = GIT_SAFE_CRLF_WARN;
+ }
+
+ *payload = git__malloc(sizeof(ca));
+ GITERR_CHECK_ALLOC(*payload);
+ memcpy(*payload, &ca, sizeof(ca));
+
+ return 0;
+}
+
+static int crlf_apply(
+ git_filter *self,
+ void **payload, /* may be read and/or set */
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ /* initialize payload in case `check` was bypassed */
+ if (!*payload) {
+ int error = crlf_check(self, payload, src, NULL);
+ if (error < 0)
+ return error;
+ }
+
+ if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return crlf_apply_to_workdir(*payload, to, from);
+ else
+ return crlf_apply_to_odb(*payload, to, from, src);
+}
+
+static void crlf_cleanup(
+ git_filter *self,
+ void *payload)
+{
+ GIT_UNUSED(self);
+ git__free(payload);
+}
+
+git_filter *git_crlf_filter_new(void)
+{
+ struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
+ if (f == NULL)
+ return NULL;
+
+ f->f.version = GIT_FILTER_VERSION;
+ f->f.attributes = "crlf eol text";
+ f->f.initialize = NULL;
+ f->f.shutdown = git_filter_free;
+ f->f.check = crlf_check;
+ f->f.apply = crlf_apply;
+ f->f.cleanup = crlf_cleanup;
+
+ return (git_filter *)f;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_CURL
+
+#include <curl/curl.h>
+
+#include "stream.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "vector.h"
+#include "proxy.h"
+
+/* This is for backwards compatibility with curl<7.45.0. */
+#ifndef CURLINFO_ACTIVESOCKET
+# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET
+# define GIT_CURL_BADSOCKET -1
+# define git_activesocket_t long
+#else
+# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD
+# define git_activesocket_t curl_socket_t
+#endif
+
+typedef struct {
+ git_stream parent;
+ CURL *handle;
+ curl_socket_t socket;
+ char curl_error[CURL_ERROR_SIZE + 1];
+ git_cert_x509 cert_info;
+ git_strarray cert_info_strings;
+ git_proxy_options proxy;
+ git_cred *proxy_cred;
+} curl_stream;
+
+static int seterr_curl(curl_stream *s)
+{
+ giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
+ return -1;
+}
+
+GIT_INLINE(int) error_no_credentials(void)
+{
+ giterr_set(GITERR_NET, "proxy authentication required, but no callback provided");
+ return GIT_EAUTH;
+}
+
+static int apply_proxy_creds(curl_stream *s)
+{
+ CURLcode res;
+ git_cred_userpass_plaintext *userpass;
+
+ if (!s->proxy_cred)
+ return GIT_ENOTFOUND;
+
+ userpass = (git_cred_userpass_plaintext *) s->proxy_cred;
+ if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK)
+ return seterr_curl(s);
+ if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK)
+ return seterr_curl(s);
+
+ return 0;
+}
+
+static int ask_and_apply_proxy_creds(curl_stream *s)
+{
+ int error;
+ git_proxy_options *opts = &s->proxy;
+
+ if (!opts->credentials)
+ return error_no_credentials();
+
+ /* TODO: see if PROXYAUTH_AVAIL helps us here */
+ git_cred_free(s->proxy_cred);
+ s->proxy_cred = NULL;
+ giterr_clear();
+ error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload);
+ if (error == GIT_PASSTHROUGH)
+ return error_no_credentials();
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "proxy authentication was aborted by the user");
+ return error;
+ }
+
+ if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
+ giterr_set(GITERR_NET, "credentials callback returned invalid credential type");
+ return -1;
+ }
+
+ return apply_proxy_creds(s);
+}
+
+static int curls_connect(git_stream *stream)
+{
+ curl_stream *s = (curl_stream *) stream;
+ git_activesocket_t sockextr;
+ long connect_last = 0;
+ int failed_cert = 0, error;
+ bool retry_connect;
+ CURLcode res;
+
+ /* Apply any credentials we've already established */
+ error = apply_proxy_creds(s);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return seterr_curl(s);
+
+ do {
+ retry_connect = 0;
+ res = curl_easy_perform(s->handle);
+
+ curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last);
+
+ /* HTTP 407 Proxy Authentication Required */
+ if (connect_last == 407) {
+ if ((error = ask_and_apply_proxy_creds(s)) < 0)
+ return error;
+
+ retry_connect = true;
+ }
+ } while (retry_connect);
+
+ if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION)
+ return seterr_curl(s);
+ if (res == CURLE_PEER_FAILED_VERIFICATION)
+ failed_cert = 1;
+
+ if ((res = curl_easy_getinfo(s->handle, CURLINFO_ACTIVESOCKET, &sockextr)) != CURLE_OK) {
+ return seterr_curl(s);
+ }
+
+ if (sockextr == GIT_CURL_BADSOCKET) {
+ giterr_set(GITERR_NET, "curl socket is no longer valid");
+ return -1;
+ }
+
+ s->socket = sockextr;
+
+ if (s->parent.encrypted && failed_cert)
+ return GIT_ECERTIFICATE;
+
+ return 0;
+}
+
+static int curls_certificate(git_cert **out, git_stream *stream)
+{
+ int error;
+ CURLcode res;
+ struct curl_slist *slist;
+ struct curl_certinfo *certinfo;
+ git_vector strings = GIT_VECTOR_INIT;
+ curl_stream *s = (curl_stream *) stream;
+
+ if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
+ return seterr_curl(s);
+
+ /* No information is available, can happen with SecureTransport */
+ if (certinfo->num_of_certs == 0) {
+ s->cert_info.parent.cert_type = GIT_CERT_NONE;
+ s->cert_info.data = NULL;
+ s->cert_info.len = 0;
+ return 0;
+ }
+
+ if ((error = git_vector_init(&strings, 8, NULL)) < 0)
+ return error;
+
+ for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
+ char *str = git__strdup(slist->data);
+ GITERR_CHECK_ALLOC(str);
+ git_vector_insert(&strings, str);
+ }
+
+ /* Copy the contents of the vector into a strarray so we can expose them */
+ s->cert_info_strings.strings = (char **) strings.contents;
+ s->cert_info_strings.count = strings.length;
+
+ s->cert_info.parent.cert_type = GIT_CERT_STRARRAY;
+ s->cert_info.data = &s->cert_info_strings;
+ s->cert_info.len = strings.length;
+
+ *out = &s->cert_info.parent;
+
+ return 0;
+}
+
+static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
+{
+ int error;
+ CURLcode res;
+ curl_stream *s = (curl_stream *) stream;
+
+ if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0)
+ return error;
+
+ if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK)
+ return seterr_curl(s);
+
+ if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK)
+ return seterr_curl(s);
+
+ return 0;
+}
+
+static int wait_for(curl_socket_t fd, bool reading)
+{
+ int ret;
+ fd_set infd, outfd, errfd;
+
+ FD_ZERO(&infd);
+ FD_ZERO(&outfd);
+ FD_ZERO(&errfd);
+
+ assert(fd >= 0);
+ FD_SET(fd, &errfd);
+ if (reading)
+ FD_SET(fd, &infd);
+ else
+ FD_SET(fd, &outfd);
+
+ if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) {
+ giterr_set(GITERR_OS, "error in select");
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ int error;
+ size_t off = 0, sent;
+ CURLcode res;
+ curl_stream *s = (curl_stream *) stream;
+
+ GIT_UNUSED(flags);
+
+ do {
+ if ((error = wait_for(s->socket, false)) < 0)
+ return error;
+
+ res = curl_easy_send(s->handle, data + off, len - off, &sent);
+ if (res == CURLE_OK)
+ off += sent;
+ } while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len);
+
+ if (res != CURLE_OK)
+ return seterr_curl(s);
+
+ return len;
+}
+
+static ssize_t curls_read(git_stream *stream, void *data, size_t len)
+{
+ int error;
+ size_t read;
+ CURLcode res;
+ curl_stream *s = (curl_stream *) stream;
+
+ do {
+ if ((error = wait_for(s->socket, true)) < 0)
+ return error;
+
+ res = curl_easy_recv(s->handle, data, len, &read);
+ } while (res == CURLE_AGAIN);
+
+ if (res != CURLE_OK)
+ return seterr_curl(s);
+
+ return read;
+}
+
+static int curls_close(git_stream *stream)
+{
+ curl_stream *s = (curl_stream *) stream;
+
+ if (!s->handle)
+ return 0;
+
+ curl_easy_cleanup(s->handle);
+ s->handle = NULL;
+ s->socket = 0;
+
+ return 0;
+}
+
+static void curls_free(git_stream *stream)
+{
+ curl_stream *s = (curl_stream *) stream;
+
+ curls_close(stream);
+ git_strarray_free(&s->cert_info_strings);
+ git__free(s);
+}
+
+int git_curl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ curl_stream *st;
+ CURL *handle;
+ int iport = 0, error;
+
+ st = git__calloc(1, sizeof(curl_stream));
+ GITERR_CHECK_ALLOC(st);
+
+ handle = curl_easy_init();
+ if (handle == NULL) {
+ giterr_set(GITERR_NET, "failed to create curl handle");
+ git__free(st);
+ return -1;
+ }
+
+ if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) {
+ git__free(st);
+ return error;
+ }
+
+ curl_easy_setopt(handle, CURLOPT_URL, host);
+ curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error);
+ curl_easy_setopt(handle, CURLOPT_PORT, iport);
+ curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1);
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1);
+ curl_easy_setopt(handle, CURLOPT_CERTINFO, 1);
+ curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1);
+ curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+
+ /* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.encrypted = 0; /* we don't encrypt ourselves */
+ st->parent.proxy_support = 1;
+ st->parent.connect = curls_connect;
+ st->parent.certificate = curls_certificate;
+ st->parent.set_proxy = curls_set_proxy;
+ st->parent.read = curls_read;
+ st->parent.write = curls_write;
+ st->parent.close = curls_close;
+ st->parent.free = curls_free;
+ st->handle = handle;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+#else
+
+#include "stream.h"
+
+int git_curl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(host);
+ GIT_UNUSED(port);
+
+ giterr_set(GITERR_NET, "curl is not supported in this version");
+ return -1;
+}
+
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_curl_stream_h__
+#define INCLUDE_curl_stream_h__
+
+#include "git2/sys/stream.h"
+
+extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "common.h"
+
+#ifndef GIT_WIN32
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+#include "cache.h"
+#include "posix.h"
+
+#include <ctype.h>
+#include <time.h>
+
+typedef enum {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822,
+ DATE_RAW
+} date_mode;
+
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+static git_time_t tm_to_time_t(const struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+ return -1;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+ { "Z", 0, 0, }, /* Zulu, alias for UTC */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast */
+ { "JST", +9, 0, }, /* Japan Standard */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+static size_t match_string(const char *date, const char *str)
+{
+ size_t i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static size_t match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ unsigned int i;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+ size_t match = match_string(date, timezone_names[i].name);
+ if (match >= 3 || match == strlen(timezone_names[i].name)) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
+ return 2;
+ }
+
+ /* BAD */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ struct tm check = *tm;
+ struct tm *r = (now_tm ? &check : tm);
+ time_t specified;
+
+ r->tm_mon = month - 1;
+ r->tm_mday = day;
+ if (year == -1) {
+ if (!now_tm)
+ return 1;
+ r->tm_year = now_tm->tm_year;
+ }
+ else if (year >= 1970 && year < 2100)
+ r->tm_year = year - 1900;
+ else if (year > 70 && year < 100)
+ r->tm_year = year;
+ else if (year < 38)
+ r->tm_year = year + 100;
+ else
+ return 0;
+ if (!now_tm)
+ return 1;
+
+ specified = tm_to_time_t(r);
+
+ /* Be it commit time or author time, it does not make
+ * sense to specify timestamp way into the future. Make
+ * sure it is not later than ten days from now...
+ */
+ if (now + 10*24*3600 < specified)
+ return 0;
+ tm->tm_mon = r->tm_mon;
+ tm->tm_mday = r->tm_mday;
+ if (year != -1)
+ tm->tm_year = r->tm_year;
+ return 1;
+ }
+ return 0;
+}
+
+static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ time_t now;
+ struct tm now_tm;
+ struct tm *refuse_future;
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ case '.':
+ now = time(NULL);
+ refuse_future = NULL;
+ if (p_gmtime_r(&now, &now_tm))
+ refuse_future = &now_tm;
+
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, refuse_future, now, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, refuse_future, now, tm))
+ break;
+ }
+ /* Our eastern European friends say dd.mm.yy[yy]
+ * is the norm there, so giving precedence to
+ * mm/dd/yy[yy] form only when separator is not '.'
+ */
+ if (c != '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+ if (is_date(num3, num2, num, refuse_future, now, tm))
+ break;
+ /* Funny European mm.dd.yy */
+ if (c == '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
+static int nodate(struct tm *tm)
+{
+ return (tm->tm_year &
+ tm->tm_mon &
+ tm->tm_mday &
+ tm->tm_hour &
+ tm->tm_min &
+ tm->tm_sec) < 0;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ size_t n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for any numbers with
+ * more than 8 digits. This is because we don't want to rule out
+ * numbers like 20070606 as a YYYYMMDD date.
+ */
+ if (num >= 100000000 && nodate(tm)) {
+ time_t time = num;
+ if (p_gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[-.:/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1400 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numbers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 13 && tm->tm_mon < 0)
+ tm->tm_mon = num-1;
+
+ return n;
+}
+
+static size_t match_tz(const char *date, int *offp)
+{
+ char *end;
+ int hour = strtoul(date + 1, &end, 10);
+ size_t n = end - (date + 1);
+ int min = 0;
+
+ if (n == 4) {
+ /* hhmm */
+ min = hour % 100;
+ hour = hour / 100;
+ } else if (n != 2) {
+ min = 99; /* random stuff */
+ } else if (*end == ':') {
+ /* hh:mm? */
+ min = strtoul(end + 1, &end, 10);
+ if (end - (date + 1) != 5)
+ min = 99; /* random stuff */
+ } /* otherwise we parsed "hh" */
+
+ /*
+ * Don't accept any random stuff. Even though some places have
+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+ * UTC+14), there is something wrong if hour part is much
+ * larger than that. We might also want to check that the
+ * minutes are divisible by 15 or something too. (Offset of
+ * Kathmandu, Nepal is UTC+5:45)
+ */
+ if (min < 60 && hour < 24) {
+ int offset = hour * 60 + min;
+ if (*date == '-')
+ offset = -offset;
+ *offp = offset;
+ }
+ return end - date;
+}
+
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
+{
+ struct tm tm;
+ int tm_gmt;
+ git_time_t dummy_timestamp;
+ int dummy_offset;
+
+ if (!timestamp)
+ timestamp = &dummy_timestamp;
+ if (!offset)
+ offset = &dummy_offset;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ tm.tm_hour = -1;
+ tm.tm_min = -1;
+ tm.tm_sec = -1;
+ *offset = -1;
+ tm_gmt = 0;
+
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
+ for (;;) {
+ size_t match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, offset);
+
+ if (!match) {
+ /* BAD */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ *timestamp = tm_to_time_t(&tm);
+ if (*offset == -1)
+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
+
+ if (*timestamp == (git_time_t)-1)
+ return -1;
+
+ if (!tm_gmt)
+ *timestamp -= *offset * 60;
+ return 0; /* success */
+}
+
+
+/*
+ * Relative time update (eg "2 days ago"). If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
+{
+ time_t n;
+
+ if (tm->tm_mday < 0)
+ tm->tm_mday = now->tm_mday;
+ if (tm->tm_mon < 0)
+ tm->tm_mon = now->tm_mon;
+ if (tm->tm_year < 0) {
+ tm->tm_year = now->tm_year;
+ if (tm->tm_mon > now->tm_mon)
+ tm->tm_year--;
+ }
+
+ n = mktime(tm) - sec;
+ p_localtime_r(&n, tm);
+ return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 0);
+}
+
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 24*60*60);
+}
+
+static void date_time(struct tm *tm, struct tm *now, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, now, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 0);
+}
+
+static void date_noon(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 12);
+}
+
+static void date_tea(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 17);
+}
+
+static void date_pm(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, struct tm *now, int *num)
+{
+ time_t n = 0;
+ GIT_UNUSED(now);
+ GIT_UNUSED(num);
+ p_localtime_r(&n, tm);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
+ { "now", date_now },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int i;
+
+ while (isalpha(*++end))
+ /* scan to non-alpha */;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ *touched = 1;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ size_t len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, now, num);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ size_t len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ *touched = 1;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4) {
+ *num = 1;
+ *touched = 1;
+ }
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ size_t len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, now, tl->length * *num);
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, now, diff * 24 * 60 * 60);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n;
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ *touched = 1;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ tm->tm_year -= *num;
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+
+ return end;
+}
+
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
+ return end;
+}
+
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+ int number = *num;
+
+ if (number) {
+ *num = 0;
+ if (tm->tm_mday < 0 && number < 32)
+ tm->tm_mday = number;
+ else if (tm->tm_mon < 0 && number < 13)
+ tm->tm_mon = number-1;
+ else if (tm->tm_year < 0) {
+ if (number > 1969 && number < 2100)
+ tm->tm_year = number - 1900;
+ else if (number > 69 && number < 100)
+ tm->tm_year = number;
+ else if (number < 38)
+ tm->tm_year = 100 + number;
+ /* We mess up for number = 00 ? */
+ }
+ }
+}
+
+static git_time_t approxidate_str(const char *date,
+ time_t time_sec,
+ int *error_ret)
+{
+ int number = 0;
+ int touched = 0;
+ struct tm tm = {0}, now;
+
+ p_localtime_r(&time_sec, &tm);
+ now = tm;
+
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ pending_number(&tm, &number);
+ date = approxidate_digit(date-1, &tm, &number);
+ touched = 1;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
+ }
+ pending_number(&tm, &number);
+ if (!touched)
+ *error_ret = 1;
+ return update_tm(&tm, &now, 0);
+}
+
+int git__date_parse(git_time_t *out, const char *date)
+{
+ time_t time_sec;
+ git_time_t timestamp;
+ int offset, error_ret=0;
+
+ if (!parse_date_basic(date, ×tamp, &offset)) {
+ *out = timestamp;
+ return 0;
+ }
+
+ if (time(&time_sec) == -1)
+ return -1;
+
+ *out = approxidate_str(date, time_sec, &error_ret);
+ return error_ret;
+}
+
+int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date)
+{
+ int written;
+ struct tm gmt;
+ time_t t;
+
+ assert(out && date);
+
+ t = (time_t) (date->time + date->offset * 60);
+
+ if (p_gmtime_r (&t, &gmt) == NULL)
+ return -1;
+
+ written = p_snprintf(out, len, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d",
+ weekday_names[gmt.tm_wday],
+ gmt.tm_mday,
+ month_names[gmt.tm_mon],
+ gmt.tm_year + 1900,
+ gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
+ date->offset / 60, date->offset % 60);
+
+ if (written < 0 || (written > (int) len - 1))
+ return -1;
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "delta.h"
+
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+ 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+ 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+ 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+ 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+ 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+ 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+ 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+ 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+ 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+ 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+ 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+ 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+ 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+ 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+ 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+ 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+ 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+ 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+ 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+ 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+ 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+ 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+ 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+ 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+ 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+ 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+ 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+ 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+ 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+ 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+ 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+ 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+ 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+ 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+ 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+ 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+ 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+ 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+ 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+ 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+ 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+ 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+ 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
+
+static const unsigned int U[256] = {
+ 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+ 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+ 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+ 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+ 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+ 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+ 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+ 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+ 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+ 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+ 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+ 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+ 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+ 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+ 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+ 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+ 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+ 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+ 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+ 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+ 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+ 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+ 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+ 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+ 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+ 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+ 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+ 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+ 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+ 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+ 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+ 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+ 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+ 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+ 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+ 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+ 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+ 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+ 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+ 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+ 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+ 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+ 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
+
+struct index_entry {
+ const unsigned char *ptr;
+ unsigned int val;
+ struct index_entry *next;
+};
+
+struct git_delta_index {
+ unsigned long memsize;
+ const void *src_buf;
+ size_t src_size;
+ unsigned int hash_mask;
+ struct index_entry *hash[GIT_FLEX_ARRAY];
+};
+
+static int lookup_index_alloc(
+ void **out, unsigned long *out_len, size_t entries, size_t hash_count)
+{
+ size_t entries_len, hash_len, index_len;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry));
+ GITERR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *));
+
+ GITERR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len);
+ GITERR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len);
+
+ if (!git__is_ulong(index_len)) {
+ giterr_set(GITERR_NOMEMORY, "Overly large delta");
+ return -1;
+ }
+
+ *out = git__malloc(index_len);
+ GITERR_CHECK_ALLOC(*out);
+
+ *out_len = index_len;
+ return 0;
+}
+
+int git_delta_index_init(
+ git_delta_index **out, const void *buf, size_t bufsize)
+{
+ unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+ const unsigned char *data, *buffer = buf;
+ struct git_delta_index *index;
+ struct index_entry *entry, **hash;
+ void *mem;
+ unsigned long memsize;
+
+ *out = NULL;
+
+ if (!buf || !bufsize)
+ return 0;
+
+ /* Determine index hash size. Note that indexing skips the
+ first byte to allow for optimizing the rabin polynomial
+ initialization in create_delta(). */
+ entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW;
+ if (bufsize >= 0xffffffffUL) {
+ /*
+ * Current delta format can't encode offsets into
+ * reference buffer with more than 32 bits.
+ */
+ entries = 0xfffffffeU / RABIN_WINDOW;
+ }
+ hsize = entries / 4;
+ for (i = 4; i < 31 && (1u << i) < hsize; i++);
+ hsize = 1 << i;
+ hmask = hsize - 1;
+
+ if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0)
+ return -1;
+
+ index = mem;
+ mem = index->hash;
+ hash = mem;
+ mem = hash + hsize;
+ entry = mem;
+
+ index->memsize = memsize;
+ index->src_buf = buf;
+ index->src_size = bufsize;
+ index->hash_mask = hmask;
+ memset(hash, 0, hsize * sizeof(*hash));
+
+ /* allocate an array to count hash entries */
+ hash_count = git__calloc(hsize, sizeof(*hash_count));
+ if (!hash_count) {
+ git__free(index);
+ return -1;
+ }
+
+ /* then populate the index */
+ prev_val = ~0;
+ for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+ data >= buffer;
+ data -= RABIN_WINDOW) {
+ unsigned int val = 0;
+ for (i = 1; i <= RABIN_WINDOW; i++)
+ val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+ if (val == prev_val) {
+ /* keep the lowest of consecutive identical blocks */
+ entry[-1].ptr = data + RABIN_WINDOW;
+ } else {
+ prev_val = val;
+ i = val & hmask;
+ entry->ptr = data + RABIN_WINDOW;
+ entry->val = val;
+ entry->next = hash[i];
+ hash[i] = entry++;
+ hash_count[i]++;
+ }
+ }
+
+ /*
+ * Determine a limit on the number of entries in the same hash
+ * bucket. This guard us against patological data sets causing
+ * really bad hash distribution with most entries in the same hash
+ * bucket that would bring us to O(m*n) computing costs (m and n
+ * corresponding to reference and target buffer sizes).
+ *
+ * Make sure none of the hash buckets has more entries than
+ * we're willing to test. Otherwise we cull the entry list
+ * uniformly to still preserve a good repartition across
+ * the reference buffer.
+ */
+ for (i = 0; i < hsize; i++) {
+ if (hash_count[i] < HASH_LIMIT)
+ continue;
+
+ entry = hash[i];
+ do {
+ struct index_entry *keep = entry;
+ int skip = hash_count[i] / HASH_LIMIT / 2;
+ do {
+ entry = entry->next;
+ } while(--skip && entry);
+ keep->next = entry;
+ } while (entry);
+ }
+ git__free(hash_count);
+
+ *out = index;
+ return 0;
+}
+
+void git_delta_index_free(git_delta_index *index)
+{
+ git__free(index);
+}
+
+size_t git_delta_index_size(git_delta_index *index)
+{
+ assert(index);
+
+ return index->memsize;
+}
+
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
+
+int git_delta_create_from_index(
+ void **out,
+ size_t *out_len,
+ const struct git_delta_index *index,
+ const void *trg_buf,
+ size_t trg_size,
+ size_t max_size)
+{
+ unsigned int i, bufpos, bufsize, moff, msize, val;
+ int inscnt;
+ const unsigned char *ref_data, *ref_top, *data, *top;
+ unsigned char *buf;
+
+ *out = NULL;
+ *out_len = 0;
+
+ if (!trg_buf || !trg_size)
+ return 0;
+
+ bufpos = 0;
+ bufsize = 8192;
+ if (max_size && bufsize >= max_size)
+ bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
+ buf = git__malloc(bufsize);
+ GITERR_CHECK_ALLOC(buf);
+
+ /* store reference buffer size */
+ i = index->src_size;
+ while (i >= 0x80) {
+ buf[bufpos++] = i | 0x80;
+ i >>= 7;
+ }
+ buf[bufpos++] = i;
+
+ /* store target buffer size */
+ i = trg_size;
+ while (i >= 0x80) {
+ buf[bufpos++] = i | 0x80;
+ i >>= 7;
+ }
+ buf[bufpos++] = i;
+
+ ref_data = index->src_buf;
+ ref_top = ref_data + index->src_size;
+ data = trg_buf;
+ top = (const unsigned char *) trg_buf + trg_size;
+
+ bufpos++;
+ val = 0;
+ for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+ buf[bufpos++] = *data;
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ }
+ inscnt = i;
+
+ moff = 0;
+ msize = 0;
+ while (data < top) {
+ if (msize < 4096) {
+ struct index_entry *entry;
+ val ^= U[data[-RABIN_WINDOW]];
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ i = val & index->hash_mask;
+ for (entry = index->hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = (unsigned int)(ref_top - ref);
+ if (entry->val != val)
+ continue;
+ if (ref_size > (unsigned int)(top - src))
+ ref_size = (unsigned int)(top - src);
+ if (ref_size <= msize)
+ break;
+ while (ref_size-- && *src++ == *ref)
+ ref++;
+ if (msize < (unsigned int)(ref - entry->ptr)) {
+ /* this is our best match so far */
+ msize = (unsigned int)(ref - entry->ptr);
+ moff = (unsigned int)(entry->ptr - ref_data);
+ if (msize >= 4096) /* good enough */
+ break;
+ }
+ }
+ }
+
+ if (msize < 4) {
+ if (!inscnt)
+ bufpos++;
+ buf[bufpos++] = *data++;
+ inscnt++;
+ if (inscnt == 0x7f) {
+ buf[bufpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+ msize = 0;
+ } else {
+ unsigned int left;
+ unsigned char *op;
+
+ if (inscnt) {
+ while (moff && ref_data[moff-1] == data[-1]) {
+ /* we can match one byte back */
+ msize++;
+ moff--;
+ data--;
+ bufpos--;
+ if (--inscnt)
+ continue;
+ bufpos--; /* remove count slot */
+ inscnt--; /* make it -1 */
+ break;
+ }
+ buf[bufpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+
+ /* A copy op is currently limited to 64KB (pack v2) */
+ left = (msize < 0x10000) ? 0 : (msize - 0x10000);
+ msize -= left;
+
+ op = buf + bufpos++;
+ i = 0x80;
+
+ if (moff & 0x000000ff)
+ buf[bufpos++] = moff >> 0, i |= 0x01;
+ if (moff & 0x0000ff00)
+ buf[bufpos++] = moff >> 8, i |= 0x02;
+ if (moff & 0x00ff0000)
+ buf[bufpos++] = moff >> 16, i |= 0x04;
+ if (moff & 0xff000000)
+ buf[bufpos++] = moff >> 24, i |= 0x08;
+
+ if (msize & 0x00ff)
+ buf[bufpos++] = msize >> 0, i |= 0x10;
+ if (msize & 0xff00)
+ buf[bufpos++] = msize >> 8, i |= 0x20;
+
+ *op = i;
+
+ data += msize;
+ moff += msize;
+ msize = left;
+
+ if (msize < 4096) {
+ int j;
+ val = 0;
+ for (j = -RABIN_WINDOW; j < 0; j++)
+ val = ((val << 8) | data[j])
+ ^ T[val >> RABIN_SHIFT];
+ }
+ }
+
+ if (bufpos >= bufsize - MAX_OP_SIZE) {
+ void *tmp = buf;
+ bufsize = bufsize * 3 / 2;
+ if (max_size && bufsize >= max_size)
+ bufsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && bufpos > max_size)
+ break;
+ buf = git__realloc(buf, bufsize);
+ if (!buf) {
+ git__free(tmp);
+ return -1;
+ }
+ }
+ }
+
+ if (inscnt)
+ buf[bufpos - inscnt - 1] = inscnt;
+
+ if (max_size && bufpos > max_size) {
+ giterr_set(GITERR_NOMEMORY, "delta would be larger than maximum size");
+ git__free(buf);
+ return GIT_EBUFS;
+ }
+
+ *out_len = bufpos;
+ *out = buf;
+ return 0;
+}
+
+/*
+* Delta application was heavily cribbed from BinaryDelta.java in JGit, which
+* itself was heavily cribbed from <code>patch-delta.c</code> in the
+* GIT project. The original delta patching code was written by
+* Nicolas Pitre <nico@cam.org>.
+*/
+
+static int hdr_sz(
+ size_t *size,
+ const unsigned char **delta,
+ const unsigned char *end)
+{
+ const unsigned char *d = *delta;
+ size_t r = 0;
+ unsigned int c, shift = 0;
+
+ do {
+ if (d == end) {
+ giterr_set(GITERR_INVALID, "truncated delta");
+ return -1;
+ }
+
+ c = *d++;
+ r |= (c & 0x7f) << shift;
+ shift += 7;
+ } while (c & 0x80);
+ *delta = d;
+ *size = r;
+ return 0;
+}
+
+int git_delta_read_header(
+ size_t *base_out,
+ size_t *result_out,
+ const unsigned char *delta,
+ size_t delta_len)
+{
+ const unsigned char *delta_end = delta + delta_len;
+ if ((hdr_sz(base_out, &delta, delta_end) < 0) ||
+ (hdr_sz(result_out, &delta, delta_end) < 0))
+ return -1;
+ return 0;
+}
+
+#define DELTA_HEADER_BUFFER_LEN 16
+int git_delta_read_header_fromstream(
+ size_t *base_sz, size_t *res_sz, git_packfile_stream *stream)
+{
+ static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN;
+ unsigned char buffer[DELTA_HEADER_BUFFER_LEN];
+ const unsigned char *delta, *delta_end;
+ size_t len;
+ ssize_t read;
+
+ len = read = 0;
+ while (len < buffer_len) {
+ read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len);
+
+ if (read == 0)
+ break;
+
+ if (read == GIT_EBUFS)
+ continue;
+
+ len += read;
+ }
+
+ delta = buffer;
+ delta_end = delta + len;
+ if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
+ (hdr_sz(res_sz, &delta, delta_end) < 0))
+ return -1;
+
+ return 0;
+}
+
+int git_delta_apply(
+ void **out,
+ size_t *out_len,
+ const unsigned char *base,
+ size_t base_len,
+ const unsigned char *delta,
+ size_t delta_len)
+{
+ const unsigned char *delta_end = delta + delta_len;
+ size_t base_sz, res_sz, alloc_sz;
+ unsigned char *res_dp;
+
+ *out = NULL;
+ *out_len = 0;
+
+ /* Check that the base size matches the data we were given;
+ * if not we would underflow while accessing data from the
+ * base object, resulting in data corruption or segfault.
+ */
+ if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) {
+ giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
+ return -1;
+ }
+
+ if (hdr_sz(&res_sz, &delta, delta_end) < 0) {
+ giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
+ res_dp = git__malloc(alloc_sz);
+ GITERR_CHECK_ALLOC(res_dp);
+
+ res_dp[res_sz] = '\0';
+ *out = res_dp;
+ *out_len = res_sz;
+
+ while (delta < delta_end) {
+ unsigned char cmd = *delta++;
+ if (cmd & 0x80) {
+ /* cmd is a copy instruction; copy from the base.
+ */
+ size_t off = 0, len = 0;
+
+ if (cmd & 0x01) off = *delta++;
+ if (cmd & 0x02) off |= *delta++ << 8UL;
+ if (cmd & 0x04) off |= *delta++ << 16UL;
+ if (cmd & 0x08) off |= *delta++ << 24UL;
+
+ if (cmd & 0x10) len = *delta++;
+ if (cmd & 0x20) len |= *delta++ << 8UL;
+ if (cmd & 0x40) len |= *delta++ << 16UL;
+ if (!len) len = 0x10000;
+
+ if (base_len < off + len || res_sz < len)
+ goto fail;
+ memcpy(res_dp, base + off, len);
+ res_dp += len;
+ res_sz -= len;
+
+ }
+ else if (cmd) {
+ /* cmd is a literal insert instruction; copy from
+ * the delta stream itself.
+ */
+ if (delta_end - delta < cmd || res_sz < cmd)
+ goto fail;
+ memcpy(res_dp, delta, cmd);
+ delta += cmd;
+ res_dp += cmd;
+ res_sz -= cmd;
+
+ }
+ else {
+ /* cmd == 0 is reserved for future encodings.
+ */
+ goto fail;
+ }
+ }
+
+ if (delta != delta_end || res_sz)
+ goto fail;
+ return 0;
+
+fail:
+ git__free(*out);
+
+ *out = NULL;
+ *out_len = 0;
+
+ giterr_set(GITERR_INVALID, "Failed to apply delta");
+ return -1;
+}
--- /dev/null
+/*
+ * diff-delta code taken from git.git. See diff-delta.c for details.
+ *
+ */
+#ifndef INCLUDE_git_delta_h__
+#define INCLUDE_git_delta_h__
+
+#include "common.h"
+#include "pack.h"
+
+typedef struct git_delta_index git_delta_index;
+
+/*
+ * git_delta_index_init: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
+ * is returned on failure. The given buffer must not be freed nor altered
+ * before free_delta_index() is called. The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern int git_delta_index_init(
+ git_delta_index **out, const void *buf, size_t bufsize);
+
+/*
+ * Free the index created by git_delta_index_init()
+ */
+extern void git_delta_index_free(git_delta_index *index);
+
+/*
+ * Returns memory usage of delta index.
+ */
+extern size_t git_delta_index_size(git_delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer. If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size. The returned buffer
+ * must be freed by the caller.
+ */
+extern int git_delta_create_from_index(
+ void **out,
+ size_t *out_size,
+ const struct git_delta_index *index,
+ const void *buf,
+ size_t bufsize,
+ size_t max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size. The returned buffer must be freed by the caller.
+ */
+GIT_INLINE(int) git_delta(
+ void **out, size_t *out_len,
+ const void *src_buf, size_t src_bufsize,
+ const void *trg_buf, size_t trg_bufsize,
+ size_t max_delta_size)
+{
+ git_delta_index *index;
+ int error = 0;
+
+ *out = NULL;
+ *out_len = 0;
+
+ if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0)
+ return error;
+
+ if (index) {
+ error = git_delta_create_from_index(out, out_len,
+ index, trg_buf, trg_bufsize, max_delta_size);
+
+ git_delta_index_free(index);
+ }
+
+ return error;
+}
+
+/* the smallest possible delta size is 4 bytes */
+#define GIT_DELTA_SIZE_MIN 4
+
+/**
+* Apply a git binary delta to recover the original content.
+* The caller is responsible for freeing the returned buffer.
+*
+* @param out the output buffer
+* @param out_len the length of the output buffer
+* @param base the base to copy from during copy instructions.
+* @param base_len number of bytes available at base.
+* @param delta the delta to execute copy/insert instructions from.
+* @param delta_len total number of bytes in the delta.
+* @return 0 on success or an error code
+*/
+extern int git_delta_apply(
+ void **out,
+ size_t *out_len,
+ const unsigned char *base,
+ size_t base_len,
+ const unsigned char *delta,
+ size_t delta_len);
+
+/**
+* Read the header of a git binary delta.
+*
+* @param base_out pointer to store the base size field.
+* @param result_out pointer to store the result size field.
+* @param delta the delta to execute copy/insert instructions from.
+* @param delta_len total number of bytes in the delta.
+* @return 0 on success or an error code
+*/
+extern int git_delta_read_header(
+ size_t *base_out,
+ size_t *result_out,
+ const unsigned char *delta,
+ size_t delta_len);
+
+/**
+ * Read the header of a git binary delta
+ *
+ * This variant reads just enough from the packfile stream to read the
+ * delta header.
+ */
+extern int git_delta_read_header_fromstream(
+ size_t *base_out,
+ size_t *result_out,
+ git_packfile_stream *stream);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/describe.h"
+#include "git2/strarray.h"
+#include "git2/diff.h"
+#include "git2/status.h"
+
+#include "common.h"
+#include "commit.h"
+#include "commit_list.h"
+#include "oidmap.h"
+#include "refs.h"
+#include "revwalk.h"
+#include "tag.h"
+#include "vector.h"
+#include "repository.h"
+
+GIT__USE_OIDMAP
+
+/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */
+
+struct commit_name {
+ git_tag *tag;
+ unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
+ unsigned name_checked:1;
+ git_oid sha1;
+ char *path;
+
+ /* Khash workaround. They original key has to still be reachable */
+ git_oid peeled;
+};
+
+static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key)
+{
+ khint_t pos = git_oidmap_lookup_index(map, key);
+
+ if (!git_oidmap_valid_index(map, pos))
+ return NULL;
+
+ return git_oidmap_value_at(map, pos);
+}
+
+static struct commit_name *find_commit_name(
+ git_oidmap *names,
+ const git_oid *peeled)
+{
+ return (struct commit_name *)(oidmap_value_bykey(names, peeled));
+}
+
+static int replace_name(
+ git_tag **tag,
+ git_repository *repo,
+ struct commit_name *e,
+ unsigned int prio,
+ const git_oid *sha1)
+{
+ git_time_t e_time = 0, t_time = 0;
+
+ if (!e || e->prio < prio)
+ return 1;
+
+ if (e->prio == 2 && prio == 2) {
+ /* Multiple annotated tags point to the same commit.
+ * Select one to keep based upon their tagger date.
+ */
+ git_tag *t = NULL;
+
+ if (!e->tag) {
+ if (git_tag_lookup(&t, repo, &e->sha1) < 0)
+ return 1;
+ e->tag = t;
+ }
+
+ if (git_tag_lookup(&t, repo, sha1) < 0)
+ return 0;
+
+ *tag = t;
+
+ if (e->tag->tagger)
+ e_time = e->tag->tagger->when.time;
+
+ if (t->tagger)
+ t_time = t->tagger->when.time;
+
+ if (e_time < t_time)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int add_to_known_names(
+ git_repository *repo,
+ git_oidmap *names,
+ const char *path,
+ const git_oid *peeled,
+ unsigned int prio,
+ const git_oid *sha1)
+{
+ struct commit_name *e = find_commit_name(names, peeled);
+ bool found = (e != NULL);
+
+ git_tag *tag = NULL;
+ if (replace_name(&tag, repo, e, prio, sha1)) {
+ if (!found) {
+ e = git__malloc(sizeof(struct commit_name));
+ GITERR_CHECK_ALLOC(e);
+
+ e->path = NULL;
+ e->tag = NULL;
+ }
+
+ if (e->tag)
+ git_tag_free(e->tag);
+ e->tag = tag;
+ e->prio = prio;
+ e->name_checked = 0;
+ git_oid_cpy(&e->sha1, sha1);
+ git__free(e->path);
+ e->path = git__strdup(path);
+ git_oid_cpy(&e->peeled, peeled);
+
+ if (!found) {
+ int ret;
+
+ git_oidmap_insert(names, &e->peeled, e, ret);
+ if (ret < 0)
+ return -1;
+ }
+ }
+ else
+ git_tag_free(tag);
+
+ return 0;
+}
+
+static int retrieve_peeled_tag_or_object_oid(
+ git_oid *peeled_out,
+ git_oid *ref_target_out,
+ git_repository *repo,
+ const char *refname)
+{
+ git_reference *ref;
+ git_object *peeled = NULL;
+ int error;
+
+ if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0)
+ return error;
+
+ if ((error = git_reference_peel(&peeled, ref, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ git_oid_cpy(ref_target_out, git_reference_target(ref));
+ git_oid_cpy(peeled_out, git_object_id(peeled));
+
+ if (git_oid_cmp(ref_target_out, peeled_out) != 0)
+ error = 1; /* The reference was pointing to a annotated tag */
+ else
+ error = 0; /* Any other object */
+
+cleanup:
+ git_reference_free(ref);
+ git_object_free(peeled);
+ return error;
+}
+
+struct git_describe_result {
+ int dirty;
+ int exact_match;
+ int fallback_to_id;
+ git_oid commit_id;
+ git_repository *repo;
+ struct commit_name *name;
+ struct possible_tag *tag;
+};
+
+struct get_name_data
+{
+ git_describe_options *opts;
+ git_repository *repo;
+ git_oidmap *names;
+ git_describe_result *result;
+};
+
+static int commit_name_dup(struct commit_name **out, struct commit_name *in)
+{
+ struct commit_name *name;
+
+ name = git__malloc(sizeof(struct commit_name));
+ GITERR_CHECK_ALLOC(name);
+
+ memcpy(name, in, sizeof(struct commit_name));
+ name->tag = NULL;
+ name->path = NULL;
+
+ if (in->tag && git_tag_dup(&name->tag, in->tag) < 0)
+ return -1;
+
+ name->path = git__strdup(in->path);
+ GITERR_CHECK_ALLOC(name->path);
+
+ *out = name;
+ return 0;
+}
+
+static int get_name(const char *refname, void *payload)
+{
+ struct get_name_data *data;
+ bool is_tag, is_annotated, all;
+ git_oid peeled, sha1;
+ unsigned int prio;
+ int error = 0;
+
+ data = (struct get_name_data *)payload;
+ is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR);
+ all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
+
+ /* Reject anything outside refs/tags/ unless --all */
+ if (!all && !is_tag)
+ return 0;
+
+ /* Accept only tags that match the pattern, if given */
+ if (data->opts->pattern && (!is_tag || p_fnmatch(data->opts->pattern,
+ refname + strlen(GIT_REFS_TAGS_DIR), 0)))
+ return 0;
+
+ /* Is it annotated? */
+ if ((error = retrieve_peeled_tag_or_object_oid(
+ &peeled, &sha1, data->repo, refname)) < 0)
+ return error;
+
+ is_annotated = error;
+
+ /*
+ * By default, we only use annotated tags, but with --tags
+ * we fall back to lightweight ones (even without --tags,
+ * we still remember lightweight ones, only to give hints
+ * in an error message). --all allows any refs to be used.
+ */
+ if (is_annotated)
+ prio = 2;
+ else if (is_tag)
+ prio = 1;
+ else
+ prio = 0;
+
+ add_to_known_names(data->repo, data->names,
+ all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR),
+ &peeled, prio, &sha1);
+ return 0;
+}
+
+struct possible_tag {
+ struct commit_name *name;
+ int depth;
+ int found_order;
+ unsigned flag_within;
+};
+
+static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in)
+{
+ struct possible_tag *tag;
+ int error;
+
+ tag = git__malloc(sizeof(struct possible_tag));
+ GITERR_CHECK_ALLOC(tag);
+
+ memcpy(tag, in, sizeof(struct possible_tag));
+ tag->name = NULL;
+
+ if ((error = commit_name_dup(&tag->name, in->name)) < 0) {
+ git__free(tag);
+ *out = NULL;
+ return error;
+ }
+
+ *out = tag;
+ return 0;
+}
+
+static int compare_pt(const void *a_, const void *b_)
+{
+ struct possible_tag *a = (struct possible_tag *)a_;
+ struct possible_tag *b = (struct possible_tag *)b_;
+ if (a->depth != b->depth)
+ return a->depth - b->depth;
+ if (a->found_order != b->found_order)
+ return a->found_order - b->found_order;
+ return 0;
+}
+
+#define SEEN (1u << 0)
+
+static unsigned long finish_depth_computation(
+ git_pqueue *list,
+ git_revwalk *walk,
+ struct possible_tag *best)
+{
+ unsigned long seen_commits = 0;
+ int error, i;
+
+ while (git_pqueue_size(list) > 0) {
+ git_commit_list_node *c = git_pqueue_pop(list);
+ seen_commits++;
+ if (c->flags & best->flag_within) {
+ size_t index = 0;
+ while (git_pqueue_size(list) > index) {
+ git_commit_list_node *i = git_pqueue_get(list, index);
+ if (!(i->flags & best->flag_within))
+ break;
+ index++;
+ }
+ if (index > git_pqueue_size(list))
+ break;
+ } else
+ best->depth++;
+ for (i = 0; i < c->out_degree; i++) {
+ git_commit_list_node *p = c->parents[i];
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+ if (!(p->flags & SEEN))
+ if ((error = git_pqueue_insert(list, p)) < 0)
+ return error;
+ p->flags |= c->flags;
+ }
+ }
+ return seen_commits;
+}
+
+static int display_name(git_buf *buf, git_repository *repo, struct commit_name *n)
+{
+ if (n->prio == 2 && !n->tag) {
+ if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) {
+ giterr_set(GITERR_TAG, "Annotated tag '%s' not available", n->path);
+ return -1;
+ }
+ }
+
+ if (n->tag && !n->name_checked) {
+ if (!git_tag_name(n->tag)) {
+ giterr_set(GITERR_TAG, "Annotated tag '%s' has no embedded name", n->path);
+ return -1;
+ }
+
+ /* TODO: Cope with warnings
+ if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
+ warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
+ */
+
+ n->name_checked = 1;
+ }
+
+ if (n->tag)
+ git_buf_printf(buf, "%s", git_tag_name(n->tag));
+ else
+ git_buf_printf(buf, "%s", n->path);
+
+ return 0;
+}
+
+static int find_unique_abbrev_size(
+ int *out,
+ git_repository *repo,
+ const git_oid *oid_in,
+ int abbreviated_size)
+{
+ size_t size = abbreviated_size;
+ git_odb *odb;
+ git_oid dummy;
+ int error;
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ return error;
+
+ while (size < GIT_OID_HEXSZ) {
+ if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) {
+ *out = (int) size;
+ return 0;
+ }
+
+ /* If the error wasn't that it's not unique, then it's a proper error */
+ if (error != GIT_EAMBIGUOUS)
+ return error;
+
+ /* Try again with a larger size */
+ size++;
+ }
+
+ /* If we didn't find any shorter prefix, we have to do the whole thing */
+ *out = GIT_OID_HEXSZ;
+
+ return 0;
+}
+
+static int show_suffix(
+ git_buf *buf,
+ int depth,
+ git_repository *repo,
+ const git_oid* id,
+ size_t abbrev_size)
+{
+ int error, size = 0;
+
+ char hex_oid[GIT_OID_HEXSZ];
+
+ if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0)
+ return error;
+
+ git_oid_fmt(hex_oid, id);
+
+ git_buf_printf(buf, "-%d-g", depth);
+
+ git_buf_put(buf, hex_oid, size);
+
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+#define MAX_CANDIDATES_TAGS FLAG_BITS - 1
+
+static int describe_not_found(const git_oid *oid, const char *message_format) {
+ char oid_str[GIT_OID_HEXSZ + 1];
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);
+
+ giterr_set(GITERR_DESCRIBE, message_format, oid_str);
+ return GIT_ENOTFOUND;
+}
+
+static int describe(
+ struct get_name_data *data,
+ git_commit *commit)
+{
+ struct commit_name *n;
+ struct possible_tag *best;
+ bool all, tags;
+ git_revwalk *walk = NULL;
+ git_pqueue list;
+ git_commit_list_node *cmit, *gave_up_on = NULL;
+ git_vector all_matches = GIT_VECTOR_INIT;
+ unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
+ unsigned long seen_commits = 0; /* TODO: Check long */
+ unsigned int unannotated_cnt = 0;
+ int error;
+
+ if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0)
+ return -1;
+
+ if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0)
+ goto cleanup;
+
+ all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
+ tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS;
+
+ git_oid_cpy(&data->result->commit_id, git_commit_id(commit));
+
+ n = find_commit_name(data->names, git_commit_id(commit));
+ if (n && (tags || all || n->prio == 2)) {
+ /*
+ * Exact match to an existing ref.
+ */
+ data->result->exact_match = 1;
+ if ((error = commit_name_dup(&data->result->name, n)) < 0)
+ goto cleanup;
+
+ goto cleanup;
+ }
+
+ if (!data->opts->max_candidates_tags) {
+ error = describe_not_found(
+ git_commit_id(commit),
+ "Cannot describe - no tag exactly matches '%s'");
+
+ goto cleanup;
+ }
+
+ if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0)
+ goto cleanup;
+
+ if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL)
+ goto cleanup;
+
+ if ((error = git_commit_list_parse(walk, cmit)) < 0)
+ goto cleanup;
+
+ cmit->flags = SEEN;
+
+ if ((error = git_pqueue_insert(&list, cmit)) < 0)
+ goto cleanup;
+
+ while (git_pqueue_size(&list) > 0)
+ {
+ int i;
+
+ git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list);
+ seen_commits++;
+
+ n = find_commit_name(data->names, &c->oid);
+
+ if (n) {
+ if (!tags && !all && n->prio < 2) {
+ unannotated_cnt++;
+ } else if (match_cnt < data->opts->max_candidates_tags) {
+ struct possible_tag *t = git__malloc(sizeof(struct commit_name));
+ GITERR_CHECK_ALLOC(t);
+ if ((error = git_vector_insert(&all_matches, t)) < 0)
+ goto cleanup;
+
+ match_cnt++;
+
+ t->name = n;
+ t->depth = seen_commits - 1;
+ t->flag_within = 1u << match_cnt;
+ t->found_order = match_cnt;
+ c->flags |= t->flag_within;
+ if (n->prio == 2)
+ annotated_cnt++;
+ }
+ else {
+ gave_up_on = c;
+ break;
+ }
+ }
+
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = git_vector_get(&all_matches, cur_match);
+ if (!(c->flags & t->flag_within))
+ t->depth++;
+ }
+
+ if (annotated_cnt && (git_pqueue_size(&list) == 0)) {
+ /*
+ if (debug) {
+ char oid_str[GIT_OID_HEXSZ + 1];
+ git_oid_tostr(oid_str, sizeof(oid_str), &c->oid);
+
+ fprintf(stderr, "finished search at %s\n", oid_str);
+ }
+ */
+ break;
+ }
+ for (i = 0; i < c->out_degree; i++) {
+ git_commit_list_node *p = c->parents[i];
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ goto cleanup;
+ if (!(p->flags & SEEN))
+ if ((error = git_pqueue_insert(&list, p)) < 0)
+ goto cleanup;
+ p->flags |= c->flags;
+
+ if (data->opts->only_follow_first_parent)
+ break;
+ }
+ }
+
+ if (!match_cnt) {
+ if (data->opts->show_commit_oid_as_fallback) {
+ data->result->fallback_to_id = 1;
+ git_oid_cpy(&data->result->commit_id, &cmit->oid);
+
+ goto cleanup;
+ }
+ if (unannotated_cnt) {
+ error = describe_not_found(git_commit_id(commit),
+ "Cannot describe - "
+ "No annotated tags can describe '%s'."
+ "However, there were unannotated tags.");
+ goto cleanup;
+ }
+ else {
+ error = describe_not_found(git_commit_id(commit),
+ "Cannot describe - "
+ "No tags can describe '%s'.");
+ goto cleanup;
+ }
+ }
+
+ git_vector_sort(&all_matches);
+
+ best = (struct possible_tag *)git_vector_get(&all_matches, 0);
+
+ if (gave_up_on) {
+ if ((error = git_pqueue_insert(&list, gave_up_on)) < 0)
+ goto cleanup;
+ seen_commits--;
+ }
+ if ((error = finish_depth_computation(
+ &list, walk, best)) < 0)
+ goto cleanup;
+
+ seen_commits += error;
+ if ((error = possible_tag_dup(&data->result->tag, best)) < 0)
+ goto cleanup;
+
+ /*
+ {
+ static const char *prio_names[] = {
+ "head", "lightweight", "annotated",
+ };
+
+ char oid_str[GIT_OID_HEXSZ + 1];
+
+ if (debug) {
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match);
+ fprintf(stderr, " %-11s %8d %s\n",
+ prio_names[t->name->prio],
+ t->depth, t->name->path);
+ }
+ fprintf(stderr, "traversed %lu commits\n", seen_commits);
+ if (gave_up_on) {
+ git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid);
+ fprintf(stderr,
+ "more than %i tags found; listed %i most recent\n"
+ "gave up search at %s\n",
+ data->opts->max_candidates_tags, data->opts->max_candidates_tags,
+ oid_str);
+ }
+ }
+ }
+ */
+
+ git_oid_cpy(&data->result->commit_id, &cmit->oid);
+
+cleanup:
+ {
+ size_t i;
+ struct possible_tag *match;
+ git_vector_foreach(&all_matches, i, match) {
+ git__free(match);
+ }
+ }
+ git_vector_free(&all_matches);
+ git_pqueue_free(&list);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int normalize_options(
+ git_describe_options *dst,
+ const git_describe_options *src)
+{
+ git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT;
+ if (!src) src = &default_options;
+
+ *dst = *src;
+
+ if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS)
+ dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS;
+
+ return 0;
+}
+
+int git_describe_commit(
+ git_describe_result **result,
+ git_object *committish,
+ git_describe_options *opts)
+{
+ struct get_name_data data;
+ struct commit_name *name;
+ git_commit *commit;
+ int error = -1;
+ git_describe_options normalized;
+
+ assert(committish);
+
+ data.result = git__calloc(1, sizeof(git_describe_result));
+ GITERR_CHECK_ALLOC(data.result);
+ data.result->repo = git_object_owner(committish);
+
+ data.repo = git_object_owner(committish);
+
+ if ((error = normalize_options(&normalized, opts)) < 0)
+ return error;
+
+ GITERR_CHECK_VERSION(
+ &normalized,
+ GIT_DESCRIBE_OPTIONS_VERSION,
+ "git_describe_options");
+ data.opts = &normalized;
+
+ data.names = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(data.names);
+
+ /** TODO: contains to be implemented */
+
+ if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_foreach_name(
+ git_object_owner(committish),
+ get_name, &data)) < 0)
+ goto cleanup;
+
+ if (git_oidmap_size(data.names) == 0 && !opts->show_commit_oid_as_fallback) {
+ giterr_set(GITERR_DESCRIBE, "Cannot describe - "
+ "No reference found, cannot describe anything.");
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = describe(&data, commit)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_commit_free(commit);
+
+ git_oidmap_foreach_value(data.names, name, {
+ git_tag_free(name->tag);
+ git__free(name->path);
+ git__free(name);
+ });
+
+ git_oidmap_free(data.names);
+
+ if (error < 0)
+ git_describe_result_free(data.result);
+ else
+ *result = data.result;
+
+ return error;
+}
+
+int git_describe_workdir(
+ git_describe_result **out,
+ git_repository *repo,
+ git_describe_options *opts)
+{
+ int error;
+ git_oid current_id;
+ git_status_list *status = NULL;
+ git_status_options status_opts = GIT_STATUS_OPTIONS_INIT;
+ git_describe_result *result = NULL;
+ git_object *commit;
+
+ if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJ_COMMIT)) < 0)
+ return error;
+
+ /* The first step is to perform a describe of HEAD, so we can leverage this */
+ if ((error = git_describe_commit(&result, commit, opts)) < 0)
+ goto out;
+
+ if ((error = git_status_list_new(&status, repo, &status_opts)) < 0)
+ goto out;
+
+
+ if (git_status_list_entrycount(status) > 0)
+ result->dirty = 1;
+
+out:
+ git_object_free(commit);
+ git_status_list_free(status);
+
+ if (error < 0)
+ git_describe_result_free(result);
+ else
+ *out = result;
+
+ return error;
+}
+
+static int normalize_format_options(
+ git_describe_format_options *dst,
+ const git_describe_format_options *src)
+{
+ if (!src) {
+ git_describe_init_format_options(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
+ return 0;
+ }
+
+ memcpy(dst, src, sizeof(git_describe_format_options));
+ return 0;
+}
+
+int git_describe_format(git_buf *out, const git_describe_result *result, const git_describe_format_options *given)
+{
+ int error;
+ git_repository *repo;
+ struct commit_name *name;
+ git_describe_format_options opts;
+
+ assert(out && result);
+
+ GITERR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options");
+ normalize_format_options(&opts, given);
+
+ git_buf_sanitize(out);
+
+
+ if (opts.always_use_long_format && opts.abbreviated_size == 0) {
+ giterr_set(GITERR_DESCRIBE, "Cannot describe - "
+ "'always_use_long_format' is incompatible with a zero"
+ "'abbreviated_size'");
+ return -1;
+ }
+
+
+ repo = result->repo;
+
+ /* If we did find an exact match, then it's the easier method */
+ if (result->exact_match) {
+ name = result->name;
+ if ((error = display_name(out, repo, name)) < 0)
+ return error;
+
+ if (opts.always_use_long_format) {
+ const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id;
+ if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0)
+ return error;
+ }
+
+ if (result->dirty && opts.dirty_suffix)
+ git_buf_puts(out, opts.dirty_suffix);
+
+ return git_buf_oom(out) ? -1 : 0;
+ }
+
+ /* If we didn't find *any* tags, we fall back to the commit's id */
+ if (result->fallback_to_id) {
+ char hex_oid[GIT_OID_HEXSZ + 1] = {0};
+ int size = 0;
+
+ if ((error = find_unique_abbrev_size(
+ &size, repo, &result->commit_id, opts.abbreviated_size)) < 0)
+ return -1;
+
+ git_oid_fmt(hex_oid, &result->commit_id);
+ git_buf_put(out, hex_oid, size);
+
+ if (result->dirty && opts.dirty_suffix)
+ git_buf_puts(out, opts.dirty_suffix);
+
+ return git_buf_oom(out) ? -1 : 0;
+ }
+
+ /* Lastly, if we found a matching tag, we show that */
+ name = result->tag->name;
+
+ if ((error = display_name(out, repo, name)) < 0)
+ return error;
+
+ if (opts.abbreviated_size) {
+ if ((error = show_suffix(out, result->tag->depth, repo,
+ &result->commit_id, opts.abbreviated_size)) < 0)
+ return error;
+ }
+
+ if (result->dirty && opts.dirty_suffix) {
+ git_buf_puts(out, opts.dirty_suffix);
+ }
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+void git_describe_result_free(git_describe_result *result)
+{
+ if (result == NULL)
+ return;
+
+ if (result->name) {
+ git_tag_free(result->name->tag);
+ git__free(result->name->path);
+ git__free(result->name);
+ }
+
+ if (result->tag) {
+ git_tag_free(result->tag->name->tag);
+ git__free(result->tag->name->path);
+ git__free(result->tag->name);
+ git__free(result->tag);
+ }
+
+ git__free(result);
+}
+
+int git_describe_init_options(git_describe_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT);
+ return 0;
+}
+
+int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/version.h"
+#include "common.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "patch.h"
+#include "commit.h"
+#include "index.h"
+
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
+ (((DIFF)->opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
+ (((DIFF)->opts.flags & (FLAG)) == 0)
+#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
+ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
+
+GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
+{
+ const char *str = delta->old_file.path;
+
+ if (!str ||
+ delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_COPIED)
+ str = delta->new_file.path;
+
+ return str;
+}
+
+const char *git_diff_delta__path(const git_diff_delta *delta)
+{
+ return diff_delta__path(delta);
+}
+
+int git_diff_delta__cmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff_delta__casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff__entry_cmp(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcmp(entry_a->path, entry_b->path);
+}
+
+int git_diff__entry_icmp(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcasecmp(entry_a->path, entry_b->path);
+}
+
+void git_diff_free(git_diff *diff)
+{
+ if (!diff)
+ return;
+
+ GIT_REFCOUNT_DEC(diff, diff->free_fn);
+}
+
+void git_diff_addref(git_diff *diff)
+{
+ GIT_REFCOUNT_INC(diff);
+}
+
+size_t git_diff_num_deltas(const git_diff *diff)
+{
+ assert(diff);
+ return diff->deltas.length;
+}
+
+size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
+{
+ size_t i, count = 0;
+ const git_diff_delta *delta;
+
+ assert(diff);
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ count += (delta->status == type);
+ }
+
+ return count;
+}
+
+const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
+{
+ assert(diff);
+ return git_vector_get(&diff->deltas, idx);
+}
+
+int git_diff_is_sorted_icase(const git_diff *diff)
+{
+ return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
+}
+
+int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
+ out->stat_calls = diff->perf.stat_calls;
+ out->oid_calculations = diff->perf.oid_calculations;
+ return 0;
+}
+
+int git_diff_format_email__append_header_tobuf(
+ git_buf *out,
+ const git_oid *id,
+ const git_signature *author,
+ const char *summary,
+ const char *body,
+ size_t patch_no,
+ size_t total_patches,
+ bool exclude_patchno_marker)
+{
+ char idstr[GIT_OID_HEXSZ + 1];
+ char date_str[GIT_DATE_RFC2822_SZ];
+ int error = 0;
+
+ git_oid_fmt(idstr, id);
+ idstr[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
+ &author->when)) < 0)
+ return error;
+
+ error = git_buf_printf(out,
+ "From %s Mon Sep 17 00:00:00 2001\n" \
+ "From: %s <%s>\n" \
+ "Date: %s\n" \
+ "Subject: ",
+ idstr,
+ author->name, author->email,
+ date_str);
+
+ if (error < 0)
+ return error;
+
+ if (!exclude_patchno_marker) {
+ if (total_patches == 1) {
+ error = git_buf_puts(out, "[PATCH] ");
+ } else {
+ error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
+ patch_no, total_patches);
+ }
+
+ if (error < 0)
+ return error;
+ }
+
+ error = git_buf_printf(out, "%s\n\n", summary);
+
+ if (body) {
+ git_buf_puts(out, body);
+
+ if (out->ptr[out->size - 1] != '\n')
+ git_buf_putc(out, '\n');
+ }
+
+ return error;
+}
+
+int git_diff_format_email__append_patches_tobuf(
+ git_buf *out,
+ git_diff *diff)
+{
+ size_t i, deltas;
+ int error = 0;
+
+ deltas = git_diff_num_deltas(diff);
+
+ for (i = 0; i < deltas; ++i) {
+ git_patch *patch = NULL;
+
+ if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
+ error = git_patch_to_buf(out, patch);
+
+ git_patch_free(patch);
+
+ if (error < 0)
+ break;
+ }
+
+ return error;
+}
+
+int git_diff_format_email(
+ git_buf *out,
+ git_diff *diff,
+ const git_diff_format_email_options *opts)
+{
+ git_diff_stats *stats = NULL;
+ char *summary = NULL, *loc = NULL;
+ bool ignore_marker;
+ unsigned int format_flags = 0;
+ size_t allocsize;
+ int error;
+
+ assert(out && diff && opts);
+ assert(opts->summary && opts->id && opts->author);
+
+ GITERR_CHECK_VERSION(opts,
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
+ "git_format_email_options");
+
+ ignore_marker = (opts->flags &
+ GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
+
+ if (!ignore_marker) {
+ if (opts->patch_no > opts->total_patches) {
+ giterr_set(GITERR_INVALID,
+ "patch %"PRIuZ" out of range. max %"PRIuZ,
+ opts->patch_no, opts->total_patches);
+ return -1;
+ }
+
+ if (opts->patch_no == 0) {
+ giterr_set(GITERR_INVALID,
+ "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
+ return -1;
+ }
+ }
+
+ /* the summary we receive may not be clean.
+ * it could potentially contain new line characters
+ * or not be set, sanitize, */
+ if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
+ size_t offset = 0;
+
+ if ((offset = (loc - opts->summary)) == 0) {
+ giterr_set(GITERR_INVALID, "summary is empty");
+ error = -1;
+ goto on_error;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
+ summary = git__calloc(allocsize, sizeof(char));
+ GITERR_CHECK_ALLOC(summary);
+
+ strncpy(summary, opts->summary, offset);
+ }
+
+ error = git_diff_format_email__append_header_tobuf(out,
+ opts->id, opts->author, summary == NULL ? opts->summary : summary,
+ opts->body, opts->patch_no, opts->total_patches, ignore_marker);
+
+ if (error < 0)
+ goto on_error;
+
+ format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
+
+ if ((error = git_buf_puts(out, "---\n")) < 0 ||
+ (error = git_diff_get_stats(&stats, diff)) < 0 ||
+ (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
+ (error = git_buf_putc(out, '\n')) < 0 ||
+ (error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0)
+ goto on_error;
+
+ error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
+
+on_error:
+ git__free(summary);
+ git_diff_stats_free(stats);
+
+ return error;
+}
+
+int git_diff_commit_as_email(
+ git_buf *out,
+ git_repository *repo,
+ git_commit *commit,
+ size_t patch_no,
+ size_t total_patches,
+ git_diff_format_email_flags_t flags,
+ const git_diff_options *diff_opts)
+{
+ git_diff *diff = NULL;
+ git_diff_format_email_options opts =
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ int error;
+
+ assert (out && repo && commit);
+
+ opts.flags = flags;
+ opts.patch_no = patch_no;
+ opts.total_patches = total_patches;
+ opts.id = git_commit_id(commit);
+ opts.summary = git_commit_summary(commit);
+ opts.body = git_commit_body(commit);
+ opts.author = git_commit_author(commit);
+
+ if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
+ return error;
+
+ error = git_diff_format_email(out, diff, &opts);
+
+ git_diff_free(diff);
+ return error;
+}
+
+int git_diff_init_options(git_diff_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
+ return 0;
+}
+
+int git_diff_find_init_options(
+ git_diff_find_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
+ return 0;
+}
+
+int git_diff_format_email_init_options(
+ git_diff_format_email_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_format_email_options,
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_h__
+#define INCLUDE_diff_h__
+
+#include "git2/diff.h"
+#include "git2/patch.h"
+#include "git2/sys/diff.h"
+#include "git2/oid.h"
+
+#include <stdio.h>
+#include "vector.h"
+#include "buffer.h"
+#include "iterator.h"
+#include "repository.h"
+#include "pool.h"
+#include "odb.h"
+
+#define DIFF_OLD_PREFIX_DEFAULT "a/"
+#define DIFF_NEW_PREFIX_DEFAULT "b/"
+
+typedef enum {
+ GIT_DIFF_TYPE_UNKNOWN = 0,
+ GIT_DIFF_TYPE_GENERATED = 1,
+ GIT_DIFF_TYPE_PARSED = 2,
+} git_diff_origin_t;
+
+struct git_diff {
+ git_refcount rc;
+ git_repository *repo;
+ git_diff_origin_t type;
+ git_diff_options opts;
+ git_vector deltas; /* vector of git_diff_delta */
+ git_pool pool;
+ git_iterator_type_t old_src;
+ git_iterator_type_t new_src;
+ git_diff_perfdata perf;
+
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+ int (*pfxcomp)(const char *str, const char *pfx);
+ int (*entrycomp)(const void *a, const void *b);
+
+ int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx);
+ void (*free_fn)(git_diff *diff);
+};
+
+extern int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int oid_strlen);
+
+extern int git_diff_delta__cmp(const void *a, const void *b);
+extern int git_diff_delta__casecmp(const void *a, const void *b);
+
+extern int git_diff__entry_cmp(const void *a, const void *b);
+extern int git_diff__entry_icmp(const void *a, const void *b);
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+
+#include "git2/attr.h"
+
+#include "diff.h"
+#include "diff_driver.h"
+#include "strmap.h"
+#include "map.h"
+#include "buf_text.h"
+#include "config.h"
+#include "repository.h"
+
+GIT__USE_STRMAP
+
+typedef enum {
+ DIFF_DRIVER_AUTO = 0,
+ DIFF_DRIVER_BINARY = 1,
+ DIFF_DRIVER_TEXT = 2,
+ DIFF_DRIVER_PATTERNLIST = 3,
+} git_diff_driver_t;
+
+typedef struct {
+ regex_t re;
+ int flags;
+} git_diff_driver_pattern;
+
+enum {
+ REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
+};
+
+/* data for finding function context for a given file type */
+struct git_diff_driver {
+ git_diff_driver_t type;
+ uint32_t binary_flags;
+ uint32_t other_flags;
+ git_array_t(git_diff_driver_pattern) fn_patterns;
+ regex_t word_pattern;
+ char name[GIT_FLEX_ARRAY];
+};
+
+#include "userdiff.h"
+
+struct git_diff_driver_registry {
+ git_strmap *drivers;
+};
+
+#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
+
+static git_diff_driver global_drivers[3] = {
+ { DIFF_DRIVER_AUTO, 0, 0, },
+ { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
+ { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 },
+};
+
+git_diff_driver_registry *git_diff_driver_registry_new(void)
+{
+ git_diff_driver_registry *reg =
+ git__calloc(1, sizeof(git_diff_driver_registry));
+ if (!reg)
+ return NULL;
+
+ if (git_strmap_alloc(®->drivers) < 0) {
+ git_diff_driver_registry_free(reg);
+ return NULL;
+ }
+
+ return reg;
+}
+
+void git_diff_driver_registry_free(git_diff_driver_registry *reg)
+{
+ git_diff_driver *drv;
+
+ if (!reg)
+ return;
+
+ git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
+ git_strmap_free(reg->drivers);
+ git__free(reg);
+}
+
+static int diff_driver_add_patterns(
+ git_diff_driver *drv, const char *regex_str, int regex_flags)
+{
+ int error = 0;
+ const char *scan, *end;
+ git_diff_driver_pattern *pat = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ for (scan = regex_str; scan; scan = end) {
+ /* get pattern to fill in */
+ if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
+ return -1;
+ }
+
+ pat->flags = regex_flags;
+ if (*scan == '!') {
+ pat->flags |= REG_NEGATE;
+ ++scan;
+ }
+
+ if ((end = strchr(scan, '\n')) != NULL) {
+ error = git_buf_set(&buf, scan, end - scan);
+ end++;
+ } else {
+ error = git_buf_sets(&buf, scan);
+ }
+ if (error < 0)
+ break;
+
+ if ((error = p_regcomp(&pat->re, buf.ptr, regex_flags)) != 0) {
+ /*
+ * TODO: issue a warning
+ */
+ }
+ }
+
+ if (error && pat != NULL)
+ (void)git_array_pop(drv->fn_patterns); /* release last item */
+ git_buf_free(&buf);
+
+ /* We want to ignore bad patterns, so return success regardless */
+ return 0;
+}
+
+static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_patterns(payload, entry->value, REG_EXTENDED);
+}
+
+static int diff_driver_funcname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_patterns(payload, entry->value, 0);
+}
+
+static git_diff_driver_registry *git_repository_driver_registry(
+ git_repository *repo)
+{
+ if (!repo->diff_drivers) {
+ git_diff_driver_registry *reg = git_diff_driver_registry_new();
+ reg = git__compare_and_swap(&repo->diff_drivers, NULL, reg);
+
+ if (reg != NULL) /* if we race, free losing allocation */
+ git_diff_driver_registry_free(reg);
+ }
+
+ if (!repo->diff_drivers)
+ giterr_set(GITERR_REPOSITORY, "Unable to create diff driver registry");
+
+ return repo->diff_drivers;
+}
+
+static int diff_driver_alloc(
+ git_diff_driver **out, size_t *namelen_out, const char *name)
+{
+ git_diff_driver *driver;
+ size_t driverlen = sizeof(git_diff_driver),
+ namelen = strlen(name),
+ alloclen;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+
+ driver = git__calloc(1, alloclen);
+ GITERR_CHECK_ALLOC(driver);
+
+ memcpy(driver->name, name, namelen);
+
+ *out = driver;
+
+ if (namelen_out)
+ *namelen_out = namelen;
+
+ return 0;
+}
+
+static int git_diff_driver_builtin(
+ git_diff_driver **out,
+ git_diff_driver_registry *reg,
+ const char *driver_name)
+{
+ int error = 0;
+ git_diff_driver_definition *ddef = NULL;
+ git_diff_driver *drv = NULL;
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) {
+ if (!strcasecmp(driver_name, builtin_defs[idx].name)) {
+ ddef = &builtin_defs[idx];
+ break;
+ }
+ }
+ if (!ddef)
+ goto done;
+
+ if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0)
+ goto done;
+
+ drv->type = DIFF_DRIVER_PATTERNLIST;
+
+ if (ddef->fns &&
+ (error = diff_driver_add_patterns(
+ drv, ddef->fns, ddef->flags | REG_EXTENDED)) < 0)
+ goto done;
+
+ if (ddef->words &&
+ (error = p_regcomp(
+ &drv->word_pattern, ddef->words, ddef->flags | REG_EXTENDED)))
+ {
+ error = giterr_set_regex(&drv->word_pattern, error);
+ goto done;
+ }
+
+ git_strmap_insert(reg->drivers, drv->name, drv, error);
+ if (error > 0)
+ error = 0;
+
+done:
+ if (error && drv)
+ git_diff_driver_free(drv);
+ else
+ *out = drv;
+
+ return error;
+}
+
+static int git_diff_driver_load(
+ git_diff_driver **out, git_repository *repo, const char *driver_name)
+{
+ int error = 0;
+ git_diff_driver_registry *reg;
+ git_diff_driver *drv = NULL;
+ size_t namelen;
+ khiter_t pos;
+ git_config *cfg = NULL;
+ git_buf name = GIT_BUF_INIT;
+ git_config_entry *ce = NULL;
+ bool found_driver = false;
+
+ if ((reg = git_repository_driver_registry(repo)) == NULL)
+ return -1;
+
+ pos = git_strmap_lookup_index(reg->drivers, driver_name);
+ if (git_strmap_valid_index(reg->drivers, pos)) {
+ *out = git_strmap_value_at(reg->drivers, pos);
+ return 0;
+ }
+
+ if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0)
+ goto done;
+
+ drv->type = DIFF_DRIVER_AUTO;
+
+ /* if you can't read config for repo, just use default driver */
+ if (git_repository_config_snapshot(&cfg, repo) < 0) {
+ giterr_clear();
+ goto done;
+ }
+
+ if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
+ goto done;
+
+ switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
+ case true:
+ /* if diff.<driver>.binary is true, just return the binary driver */
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ goto done;
+ case false:
+ /* if diff.<driver>.binary is false, force binary checks off */
+ /* but still may have custom function context patterns, etc. */
+ drv->binary_flags = GIT_DIFF_FORCE_TEXT;
+ found_driver = true;
+ break;
+ default:
+ /* diff.<driver>.binary unspecified or "auto", so just continue */
+ break;
+ }
+
+ /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "xfuncname", strlen("xfuncname"));
+ if ((error = git_config_get_multivar_foreach(
+ cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "funcname", strlen("funcname"));
+ if ((error = git_config_get_multivar_foreach(
+ cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.funcname, so just continue */
+ }
+
+ /* if we found any patterns, set driver type to use correct callback */
+ if (git_array_size(drv->fn_patterns) > 0) {
+ drv->type = DIFF_DRIVER_PATTERNLIST;
+ found_driver = true;
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "wordregex", strlen("wordregex"));
+ if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
+ goto done;
+ if (!ce || !ce->value)
+ /* no diff.<driver>.wordregex, so just continue */;
+ else if (!(error = p_regcomp(&drv->word_pattern, ce->value, REG_EXTENDED)))
+ found_driver = true;
+ else {
+ /* TODO: warn about bad regex instead of failure */
+ error = giterr_set_regex(&drv->word_pattern, error);
+ goto done;
+ }
+
+ /* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
+ * diff in drv->other_flags
+ */
+
+ /* if no driver config found at all, fall back on AUTO driver */
+ if (!found_driver)
+ goto done;
+
+ /* store driver in registry */
+ git_strmap_insert(reg->drivers, drv->name, drv, error);
+ if (error < 0)
+ goto done;
+ error = 0;
+
+ *out = drv;
+
+done:
+ git_config_entry_free(ce);
+ git_buf_free(&name);
+ git_config_free(cfg);
+
+ if (!*out) {
+ int error2 = git_diff_driver_builtin(out, reg, driver_name);
+ if (!error)
+ error = error2;
+ }
+
+ if (drv && drv != *out)
+ git_diff_driver_free(drv);
+
+ return error;
+}
+
+int git_diff_driver_lookup(
+ git_diff_driver **out, git_repository *repo, const char *path)
+{
+ int error = 0;
+ const char *value;
+
+ assert(out);
+ *out = NULL;
+
+ if (!repo || !path || !strlen(path))
+ /* just use the auto value */;
+ else if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0)
+ /* return error below */;
+ else if (GIT_ATTR_UNSPECIFIED(value))
+ /* just use the auto value */;
+ else if (GIT_ATTR_FALSE(value))
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ else if (GIT_ATTR_TRUE(value))
+ *out = &global_drivers[DIFF_DRIVER_TEXT];
+
+ /* otherwise look for driver information in config and build driver */
+ else if ((error = git_diff_driver_load(out, repo, value)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+ }
+
+ if (!*out)
+ *out = &global_drivers[DIFF_DRIVER_AUTO];
+
+ return error;
+}
+
+void git_diff_driver_free(git_diff_driver *driver)
+{
+ size_t i;
+
+ if (!driver)
+ return;
+
+ for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
+ regfree(& git_array_get(driver->fn_patterns, i)->re);
+ git_array_clear(driver->fn_patterns);
+
+ regfree(&driver->word_pattern);
+
+ git__free(driver);
+}
+
+void git_diff_driver_update_options(
+ uint32_t *option_flags, git_diff_driver *driver)
+{
+ if ((*option_flags & FORCE_DIFFABLE) == 0)
+ *option_flags |= driver->binary_flags;
+
+ *option_flags |= driver->other_flags;
+}
+
+int git_diff_driver_content_is_binary(
+ git_diff_driver *driver, const char *content, size_t content_len)
+{
+ git_buf search = GIT_BUF_INIT;
+
+ GIT_UNUSED(driver);
+
+ git_buf_attach_notowned(&search, content,
+ min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL));
+
+ /* TODO: provide encoding / binary detection callbacks that can
+ * be UTF-8 aware, etc. For now, instead of trying to be smart,
+ * let's just use the simple NUL-byte detection that core git uses.
+ */
+
+ /* previously was: if (git_buf_text_is_binary(&search)) */
+ if (git_buf_text_contains_nul(&search))
+ return 1;
+
+ return 0;
+}
+
+static int diff_context_line__simple(
+ git_diff_driver *driver, git_buf *line)
+{
+ char firstch = line->ptr[0];
+ GIT_UNUSED(driver);
+ return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
+}
+
+static int diff_context_line__pattern_match(
+ git_diff_driver *driver, git_buf *line)
+{
+ size_t i, maxi = git_array_size(driver->fn_patterns);
+ regmatch_t pmatch[2];
+
+ for (i = 0; i < maxi; ++i) {
+ git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
+
+ if (!regexec(&pat->re, line->ptr, 2, pmatch, 0)) {
+ if (pat->flags & REG_NEGATE)
+ return false;
+
+ /* use pmatch data to trim line data */
+ i = (pmatch[1].rm_so >= 0) ? 1 : 0;
+ git_buf_consume(line, git_buf_cstr(line) + pmatch[i].rm_so);
+ git_buf_truncate(line, pmatch[i].rm_eo - pmatch[i].rm_so);
+ git_buf_rtrim(line);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static long diff_context_find(
+ const char *line,
+ long line_len,
+ char *out,
+ long out_size,
+ void *payload)
+{
+ git_diff_find_context_payload *ctxt = payload;
+
+ if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
+ return -1;
+ git_buf_rtrim(&ctxt->line);
+
+ if (!ctxt->line.size)
+ return -1;
+
+ if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
+ return -1;
+
+ if (out_size > (long)ctxt->line.size)
+ out_size = (long)ctxt->line.size;
+ memcpy(out, ctxt->line.ptr, (size_t)out_size);
+
+ return out_size;
+}
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver)
+{
+ *findfn_out = driver ? diff_context_find : NULL;
+
+ memset(payload_out, 0, sizeof(*payload_out));
+ if (driver) {
+ payload_out->driver = driver;
+ payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
+ diff_context_line__pattern_match : diff_context_line__simple;
+ git_buf_init(&payload_out->line, 0);
+ }
+}
+
+void git_diff_find_context_clear(git_diff_find_context_payload *payload)
+{
+ if (payload) {
+ git_buf_free(&payload->line);
+ payload->driver = NULL;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_driver_h__
+#define INCLUDE_diff_driver_h__
+
+#include "common.h"
+#include "buffer.h"
+
+typedef struct git_diff_driver_registry git_diff_driver_registry;
+
+git_diff_driver_registry *git_diff_driver_registry_new(void);
+void git_diff_driver_registry_free(git_diff_driver_registry *);
+
+typedef struct git_diff_driver git_diff_driver;
+
+int git_diff_driver_lookup(git_diff_driver **, git_repository *, const char *);
+void git_diff_driver_free(git_diff_driver *);
+
+/* diff option flags to force off and on for this driver */
+void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *);
+
+/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */
+int git_diff_driver_content_is_binary(
+ git_diff_driver *, const char *content, size_t content_len);
+
+typedef long (*git_diff_find_context_fn)(
+ const char *, long, char *, long, void *);
+
+typedef int (*git_diff_find_context_line)(
+ git_diff_driver *, git_buf *);
+
+typedef struct {
+ git_diff_driver *driver;
+ git_diff_find_context_line match_line;
+ git_buf line;
+} git_diff_find_context_payload;
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver);
+
+void git_diff_find_context_clear(git_diff_find_context_payload *);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/blob.h"
+#include "git2/submodule.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "diff_file.h"
+#include "odb.h"
+#include "fileops.h"
+#include "filter.h"
+
+#define DIFF_MAX_FILESIZE 0x20000000
+
+static bool diff_file_content_binary_by_size(git_diff_file_content *fc)
+{
+ /* if we have diff opts, check max_size vs file size */
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 &&
+ fc->opts_max_size > 0 &&
+ fc->file->size > fc->opts_max_size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+
+ return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0);
+}
+
+static void diff_file_content_binary_by_content(git_diff_file_content *fc)
+{
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ switch (git_diff_driver_content_is_binary(
+ fc->driver, fc->map.data, fc->map.len)) {
+ case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break;
+ case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break;
+ default: break;
+ }
+}
+
+static int diff_file_content_init_common(
+ git_diff_file_content *fc, const git_diff_options *opts)
+{
+ fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL;
+
+ if (opts && opts->max_size >= 0)
+ fc->opts_max_size = opts->max_size ?
+ opts->max_size : DIFF_MAX_FILESIZE;
+
+ if (fc->src == GIT_ITERATOR_TYPE_EMPTY)
+ fc->src = GIT_ITERATOR_TYPE_TREE;
+
+ if (!fc->driver &&
+ git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ /* give driver a chance to modify options */
+ git_diff_driver_update_options(&fc->opts_flags, fc->driver);
+
+ /* make sure file is conceivable mmap-able */
+ if ((git_off_t)((size_t)fc->file->size) != fc->file->size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ /* check if user is forcing text diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ }
+ /* check if user is forcing binary diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ }
+
+ diff_file_content_binary_by_size(fc);
+
+ if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->map.len = 0;
+ fc->map.data = "";
+ }
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff *diff,
+ git_diff_delta *delta,
+ bool use_old)
+{
+ bool has_data = true;
+
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = diff->repo;
+ fc->file = use_old ? &delta->old_file : &delta->new_file;
+ fc->src = use_old ? diff->old_src : diff->new_src;
+
+ if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ has_data = !use_old; break;
+ case GIT_DELTA_DELETED:
+ has_data = use_old; break;
+ case GIT_DELTA_UNTRACKED:
+ has_data = !use_old &&
+ (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0;
+ break;
+ case GIT_DELTA_UNREADABLE:
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_COPIED:
+ case GIT_DELTA_RENAMED:
+ break;
+ default:
+ has_data = false;
+ break;
+ }
+
+ if (!has_data)
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+
+ return diff_file_content_init_common(fc, &diff->opts);
+}
+
+int git_diff_file_content__init_from_src(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_diff_file_content_src *src,
+ git_diff_file *as_file)
+{
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = repo;
+ fc->file = as_file;
+ fc->blob = src->blob;
+
+ if (!src->blob && !src->buf) {
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+ } else {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ fc->file->mode = GIT_FILEMODE_BLOB;
+
+ if (src->blob) {
+ fc->file->size = git_blob_rawsize(src->blob);
+ git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
+ fc->file->id_abbrev = GIT_OID_HEXSZ;
+
+ fc->map.len = (size_t)fc->file->size;
+ fc->map.data = (char *)git_blob_rawcontent(src->blob);
+ } else {
+ fc->file->size = src->buflen;
+ git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB);
+ fc->file->id_abbrev = GIT_OID_HEXSZ;
+
+ fc->map.len = src->buflen;
+ fc->map.data = (char *)src->buf;
+ }
+ }
+
+ return diff_file_content_init_common(fc, opts);
+}
+
+static int diff_file_content_commit_to_str(
+ git_diff_file_content *fc, bool check_status)
+{
+ char oid[GIT_OID_HEXSZ+1];
+ git_buf content = GIT_BUF_INIT;
+ const char *status = "";
+
+ if (check_status) {
+ int error = 0;
+ git_submodule *sm = NULL;
+ unsigned int sm_status = 0;
+ const git_oid *sm_head;
+
+ if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) {
+ /* GIT_EEXISTS means a "submodule" that has not been git added */
+ if (error == GIT_EEXISTS) {
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ /* update OID if we didn't have it previously */
+ if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 &&
+ ((sm_head = git_submodule_wd_id(sm)) != NULL ||
+ (sm_head = git_submodule_head_id(sm)) != NULL))
+ {
+ git_oid_cpy(&fc->file->id, sm_head);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ status = "-dirty";
+
+ git_submodule_free(sm);
+ }
+
+ git_oid_tostr(oid, sizeof(oid), &fc->file->id);
+ if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0)
+ return -1;
+
+ fc->map.len = git_buf_len(&content);
+ fc->map.data = git_buf_detach(&content);
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ return 0;
+}
+
+static int diff_file_content_load_blob(
+ git_diff_file_content *fc,
+ git_diff_options *opts)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+
+ if (git_oid_iszero(&fc->file->id))
+ return 0;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, false);
+
+ /* if we don't know size, try to peek at object header first */
+ if (!fc->file->size) {
+ if ((error = git_diff_file__resolve_zero_size(
+ fc->file, &odb_obj, fc->repo)) < 0)
+ return error;
+ }
+
+ if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 &&
+ diff_file_content_binary_by_size(fc))
+ return 0;
+
+ if (odb_obj != NULL) {
+ error = git_object__from_odb_object(
+ (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJ_BLOB);
+ git_odb_object_free(odb_obj);
+ } else {
+ error = git_blob_lookup(
+ (git_blob **)&fc->blob, fc->repo, &fc->file->id);
+ }
+
+ if (!error) {
+ fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
+ fc->map.data = (void *)git_blob_rawcontent(fc->blob);
+ fc->map.len = (size_t)git_blob_rawsize(fc->blob);
+ }
+
+ return error;
+}
+
+static int diff_file_content_load_workdir_symlink_fake(
+ git_diff_file_content *fc, git_buf *path)
+{
+ git_buf target = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_futils_readbuffer(&target, path->ptr)) < 0)
+ return error;
+
+ fc->map.len = git_buf_len(&target);
+ fc->map.data = git_buf_detach(&target);
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ git_buf_free(&target);
+ return error;
+}
+
+static int diff_file_content_load_workdir_symlink(
+ git_diff_file_content *fc, git_buf *path)
+{
+ ssize_t alloc_len, read_len;
+ int symlink_supported, error;
+
+ if ((error = git_repository__cvar(
+ &symlink_supported, fc->repo, GIT_CVAR_SYMLINKS)) < 0)
+ return -1;
+
+ if (!symlink_supported)
+ return diff_file_content_load_workdir_symlink_fake(fc, path);
+
+ /* link path on disk could be UTF-16, so prepare a buffer that is
+ * big enough to handle some UTF-8 data expansion
+ */
+ alloc_len = (ssize_t)(fc->file->size * 2) + 1;
+
+ fc->map.data = git__calloc(alloc_len, sizeof(char));
+ GITERR_CHECK_ALLOC(fc->map.data);
+
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len);
+ if (read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path);
+ return -1;
+ }
+
+ fc->map.len = read_len;
+ return 0;
+}
+
+static int diff_file_content_load_workdir_file(
+ git_diff_file_content *fc,
+ git_buf *path,
+ git_diff_options *diff_opts)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+ git_file fd = git_futils_open_ro(git_buf_cstr(path));
+ git_buf raw = GIT_BUF_INIT;
+
+ if (fd < 0)
+ return fd;
+
+ if (!fc->file->size &&
+ !(fc->file->size = git_futils_filesize(fd)))
+ goto cleanup;
+
+ if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 &&
+ diff_file_content_binary_by_size(fc))
+ goto cleanup;
+
+ if ((error = git_filter_list_load(
+ &fl, fc->repo, NULL, fc->file->path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0)
+ goto cleanup;
+
+ /* if there are no filters, try to mmap the file */
+ if (fl == NULL) {
+ if (!(error = git_futils_mmap_ro(
+ &fc->map, fd, 0, (size_t)fc->file->size))) {
+ fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
+ goto cleanup;
+ }
+
+ /* if mmap failed, fall through to try readbuffer below */
+ giterr_clear();
+ }
+
+ if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) {
+ git_buf out = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_data(&out, fl, &raw);
+
+ if (out.ptr != raw.ptr)
+ git_buf_free(&raw);
+
+ if (!error) {
+ fc->map.len = out.size;
+ fc->map.data = out.ptr;
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ }
+ }
+
+cleanup:
+ git_filter_list_free(fl);
+ p_close(fd);
+
+ return error;
+}
+
+static int diff_file_content_load_workdir(
+ git_diff_file_content *fc,
+ git_diff_options *diff_opts)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, true);
+
+ if (fc->file->mode == GIT_FILEMODE_TREE)
+ return 0;
+
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(fc->repo), fc->file->path) < 0)
+ return -1;
+
+ if (S_ISLNK(fc->file->mode))
+ error = diff_file_content_load_workdir_symlink(fc, &path);
+ else
+ error = diff_file_content_load_workdir_file(fc, &path, diff_opts);
+
+ /* once data is loaded, update OID if we didn't have it previously */
+ if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) {
+ error = git_odb_hash(
+ &fc->file->id, fc->map.data, fc->map.len, GIT_OBJ_BLOB);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_diff_file_content__load(
+ git_diff_file_content *fc,
+ git_diff_options *diff_opts)
+{
+ int error = 0;
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ return 0;
+
+ if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
+ (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0)
+ return 0;
+
+ if (fc->src == GIT_ITERATOR_TYPE_WORKDIR)
+ error = diff_file_content_load_workdir(fc, diff_opts);
+ else
+ error = diff_file_content_load_blob(fc, diff_opts);
+ if (error)
+ return error;
+
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+void git_diff_file_content__unload(git_diff_file_content *fc)
+{
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0)
+ return;
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) {
+ git__free(fc->map.data);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
+ }
+ else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
+ git_futils_mmap_free(&fc->map);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
+ }
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) {
+ git_blob_free((git_blob *)fc->blob);
+ fc->blob = NULL;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB;
+ }
+
+ fc->flags &= ~GIT_DIFF_FLAG__LOADED;
+}
+
+void git_diff_file_content__clear(git_diff_file_content *fc)
+{
+ git_diff_file_content__unload(fc);
+
+ /* for now, nothing else to do */
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_file_h__
+#define INCLUDE_diff_file_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_driver.h"
+#include "map.h"
+
+/* expanded information for one side of a delta */
+typedef struct {
+ git_repository *repo;
+ git_diff_file *file;
+ git_diff_driver *driver;
+ uint32_t flags;
+ uint32_t opts_flags;
+ git_off_t opts_max_size;
+ git_iterator_type_t src;
+ const git_blob *blob;
+ git_map map;
+} git_diff_file_content;
+
+extern int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff *diff,
+ git_diff_delta *delta,
+ bool use_old);
+
+typedef struct {
+ const git_blob *blob;
+ const void *buf;
+ size_t buflen;
+ const char *as_path;
+} git_diff_file_content_src;
+
+#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) }
+#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) }
+
+extern int git_diff_file_content__init_from_src(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_diff_file_content_src *src,
+ git_diff_file *as_file);
+
+/* this loads the blob/file-on-disk as needed */
+extern int git_diff_file_content__load(
+ git_diff_file_content *fc,
+ git_diff_options *diff_opts);
+
+/* this releases the blob/file-in-memory */
+extern void git_diff_file_content__unload(git_diff_file_content *fc);
+
+/* this unloads and also releases any other resources */
+extern void git_diff_file_content__clear(git_diff_file_content *fc);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "patch_generate.h"
+#include "fileops.h"
+#include "config.h"
+#include "attr_file.h"
+#include "filter.h"
+#include "pathspec.h"
+#include "index.h"
+#include "odb.h"
+#include "submodule.h"
+
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
+ (((DIFF)->base.opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
+ (((DIFF)->base.opts.flags & (FLAG)) == 0)
+#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \
+ (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \
+ ((DIFF)->base.opts.flags & ~(VAL))
+
+typedef struct {
+ struct git_diff base;
+
+ git_vector pathspec;
+
+ uint32_t diffcaps;
+ bool index_updated;
+} git_diff_generated;
+
+static git_diff_delta *diff_delta__alloc(
+ git_diff_generated *diff,
+ git_delta_t status,
+ const char *path)
+{
+ git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
+ if (!delta)
+ return NULL;
+
+ delta->old_file.path = git_pool_strdup(&diff->base.pool, path);
+ if (delta->old_file.path == NULL) {
+ git__free(delta);
+ return NULL;
+ }
+
+ delta->new_file.path = delta->old_file.path;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ switch (status) {
+ case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
+ case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
+ default: break; /* leave other status values alone */
+ }
+ }
+ delta->status = status;
+
+ return delta;
+}
+
+static int diff_insert_delta(
+ git_diff_generated *diff,
+ git_diff_delta *delta,
+ const char *matched_pathspec)
+{
+ int error = 0;
+
+ if (diff->base.opts.notify_cb) {
+ error = diff->base.opts.notify_cb(
+ &diff->base, delta, matched_pathspec, diff->base.opts.payload);
+
+ if (error) {
+ git__free(delta);
+
+ if (error > 0) /* positive value means to skip this delta */
+ return 0;
+ else /* negative value means to cancel diff */
+ return giterr_set_after_callback_function(error, "git_diff");
+ }
+ }
+
+ if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0)
+ git__free(delta);
+
+ return error;
+}
+
+static bool diff_pathspec_match(
+ const char **matched_pathspec,
+ git_diff_generated *diff,
+ const git_index_entry *entry)
+{
+ bool disable_pathspec_match =
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH);
+
+ /* If we're disabling fnmatch, then the iterator has already applied
+ * the filters to the files for us and we don't have to do anything.
+ * However, this only applies to *files* - the iterator will include
+ * directories that we need to recurse into when not autoexpanding,
+ * so we still need to apply the pathspec match to directories.
+ */
+ if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) &&
+ disable_pathspec_match) {
+ *matched_pathspec = entry->path;
+ return true;
+ }
+
+ return git_pathspec__match(
+ &diff->pathspec, entry->path, disable_pathspec_match,
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
+ matched_pathspec, NULL);
+}
+
+static int diff_delta__from_one(
+ git_diff_generated *diff,
+ git_delta_t status,
+ const git_index_entry *oitem,
+ const git_index_entry *nitem)
+{
+ const git_index_entry *entry = nitem;
+ bool has_old = false;
+ git_diff_delta *delta;
+ const char *matched_pathspec;
+
+ assert((oitem != NULL) ^ (nitem != NULL));
+
+ if (oitem) {
+ entry = oitem;
+ has_old = true;
+ }
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
+ has_old = !has_old;
+
+ if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
+ return 0;
+
+ if (status == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
+ return 0;
+
+ if (status == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
+ return 0;
+
+ if (status == GIT_DELTA_UNREADABLE &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
+ return 0;
+
+ if (!diff_pathspec_match(&matched_pathspec, diff, entry))
+ return 0;
+
+ delta = diff_delta__alloc(diff, status, entry->path);
+ GITERR_CHECK_ALLOC(delta);
+
+ /* This fn is just for single-sided diffs */
+ assert(status != GIT_DELTA_MODIFIED);
+ delta->nfiles = 1;
+
+ if (has_old) {
+ delta->old_file.mode = entry->mode;
+ delta->old_file.size = entry->file_size;
+ delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
+ git_oid_cpy(&delta->old_file.id, &entry->id);
+ delta->old_file.id_abbrev = GIT_OID_HEXSZ;
+ } else /* ADDED, IGNORED, UNTRACKED */ {
+ delta->new_file.mode = entry->mode;
+ delta->new_file.size = entry->file_size;
+ delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
+ git_oid_cpy(&delta->new_file.id, &entry->id);
+ delta->new_file.id_abbrev = GIT_OID_HEXSZ;
+ }
+
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (has_old || !git_oid_iszero(&delta->new_file.id))
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ return diff_insert_delta(diff, delta, matched_pathspec);
+}
+
+static int diff_delta__from_two(
+ git_diff_generated *diff,
+ git_delta_t status,
+ const git_index_entry *old_entry,
+ uint32_t old_mode,
+ const git_index_entry *new_entry,
+ uint32_t new_mode,
+ const git_oid *new_id,
+ const char *matched_pathspec)
+{
+ const git_oid *old_id = &old_entry->id;
+ git_diff_delta *delta;
+ const char *canonical_path = old_entry->path;
+
+ if (status == GIT_DELTA_UNMODIFIED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
+ return 0;
+
+ if (!new_id)
+ new_id = &new_entry->id;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ uint32_t temp_mode = old_mode;
+ const git_index_entry *temp_entry = old_entry;
+ const git_oid *temp_id = old_id;
+
+ old_entry = new_entry;
+ new_entry = temp_entry;
+ old_mode = new_mode;
+ new_mode = temp_mode;
+ old_id = new_id;
+ new_id = temp_id;
+ }
+
+ delta = diff_delta__alloc(diff, status, canonical_path);
+ GITERR_CHECK_ALLOC(delta);
+ delta->nfiles = 2;
+
+ if (!git_index_entry_is_conflict(old_entry)) {
+ delta->old_file.size = old_entry->file_size;
+ delta->old_file.mode = old_mode;
+ git_oid_cpy(&delta->old_file.id, old_id);
+ delta->old_file.id_abbrev = GIT_OID_HEXSZ;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
+ GIT_DIFF_FLAG_EXISTS;
+ }
+
+ if (!git_index_entry_is_conflict(new_entry)) {
+ git_oid_cpy(&delta->new_file.id, new_id);
+ delta->new_file.id_abbrev = GIT_OID_HEXSZ;
+ delta->new_file.size = new_entry->file_size;
+ delta->new_file.mode = new_mode;
+ delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
+ delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
+
+ if (!git_oid_iszero(&new_entry->id))
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ return diff_insert_delta(diff, delta, matched_pathspec);
+}
+
+static git_diff_delta *diff_delta__last_for_item(
+ git_diff_generated *diff,
+ const git_index_entry *item)
+{
+ git_diff_delta *delta = git_vector_last(&diff->base.deltas);
+ if (!delta)
+ return NULL;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_ADDED:
+ if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_UNREADABLE:
+ case GIT_DELTA_UNTRACKED:
+ if (diff->base.strcomp(delta->new_file.path, item->path) == 0 &&
+ git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_MODIFIED:
+ if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
+ git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
+{
+ size_t len = strlen(prefix);
+
+ /* append '/' at end if needed */
+ if (len > 0 && prefix[len - 1] != '/')
+ return git_pool_strcat(pool, prefix, "/");
+ else
+ return git_pool_strndup(pool, prefix, len + 1);
+}
+
+GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
+{
+ return delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path;
+}
+
+int git_diff_delta__i2w_cmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff_delta__i2w_casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ if (delta->status == GIT_DELTA_UNMODIFIED &&
+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_IGNORED &&
+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_UNTRACKED &&
+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_UNREADABLE &&
+ (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
+ return true;
+
+ return false;
+}
+
+
+static const char *diff_mnemonic_prefix(
+ git_iterator_type_t type, bool left_side)
+{
+ const char *pfx = "";
+
+ switch (type) {
+ case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
+ case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
+ case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
+ default: break;
+ }
+
+ /* note: without a deeper look at pathspecs, there is no easy way
+ * to get the (o)bject / (w)ork tree mnemonics working...
+ */
+
+ return pfx;
+}
+
+void git_diff__set_ignore_case(git_diff *diff, bool ignore_case)
+{
+ if (!ignore_case) {
+ diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;
+
+ diff->strcomp = git__strcmp;
+ diff->strncomp = git__strncmp;
+ diff->pfxcomp = git__prefixcmp;
+ diff->entrycomp = git_diff__entry_cmp;
+
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
+ } else {
+ diff->opts.flags |= GIT_DIFF_IGNORE_CASE;
+
+ diff->strcomp = git__strcasecmp;
+ diff->strncomp = git__strncasecmp;
+ diff->pfxcomp = git__prefixcmp_icase;
+ diff->entrycomp = git_diff__entry_icmp;
+
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
+ }
+
+ git_vector_sort(&diff->deltas);
+}
+
+static void diff_generated_free(git_diff *d)
+{
+ git_diff_generated *diff = (git_diff_generated *)d;
+
+ git_vector_free_deep(&diff->base.deltas);
+
+ git_pathspec__vfree(&diff->pathspec);
+ git_pool_clear(&diff->base.pool);
+
+ git__memzero(diff, sizeof(*diff));
+ git__free(diff);
+}
+
+static git_diff_generated *diff_generated_alloc(
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter)
+{
+ git_diff_generated *diff;
+ git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
+
+ assert(repo && old_iter && new_iter);
+
+ if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL)
+ return NULL;
+
+ GIT_REFCOUNT_INC(diff);
+ diff->base.type = GIT_DIFF_TYPE_GENERATED;
+ diff->base.repo = repo;
+ diff->base.old_src = old_iter->type;
+ diff->base.new_src = new_iter->type;
+ diff->base.patch_fn = git_patch_generated_from_diff;
+ diff->base.free_fn = diff_generated_free;
+ memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options));
+
+ git_pool_init(&diff->base.pool, 1);
+
+ if (git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
+ git_diff_free(&diff->base);
+ return NULL;
+ }
+
+ /* Use case-insensitive compare if either iterator has
+ * the ignore_case bit set */
+ git_diff__set_ignore_case(
+ &diff->base,
+ git_iterator_ignore_case(old_iter) ||
+ git_iterator_ignore_case(new_iter));
+
+ return diff;
+}
+
+static int diff_generated_apply_options(
+ git_diff_generated *diff,
+ const git_diff_options *opts)
+{
+ git_config *cfg = NULL;
+ git_repository *repo = diff->base.repo;
+ git_pool *pool = &diff->base.pool;
+ int val;
+
+ if (opts) {
+ /* copy user options (except case sensitivity info from iterators) */
+ bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
+ memcpy(&diff->base.opts, opts, sizeof(diff->base.opts));
+ DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
+
+ /* initialize pathspec from options */
+ if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
+ return -1;
+ }
+
+ /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
+ diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
+
+ /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
+ diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /* load config values that affect diff behavior */
+ if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
+ return val;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
+ diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
+ diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT;
+
+ if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
+ !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
+ diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
+ diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME;
+
+ /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
+
+ /* If not given explicit `opts`, check `diff.xyz` configs */
+ if (!opts) {
+ int context = git_config__get_int_force(cfg, "diff.context", 3);
+ diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3;
+
+ /* add other defaults here */
+ }
+
+ /* Reverse src info if diff is reversed */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ git_iterator_type_t tmp_src = diff->base.old_src;
+ diff->base.old_src = diff->base.new_src;
+ diff->base.new_src = tmp_src;
+ }
+
+ /* Unset UPDATE_INDEX unless diffing workdir and index */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
+ (!(diff->base.old_src == GIT_ITERATOR_TYPE_WORKDIR ||
+ diff->base.new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
+ !(diff->base.old_src == GIT_ITERATOR_TYPE_INDEX ||
+ diff->base.new_src == GIT_ITERATOR_TYPE_INDEX)))
+ diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
+
+ /* if ignore_submodules not explicitly set, check diff config */
+ if (diff->base.opts.ignore_submodules <= 0) {
+ git_config_entry *entry;
+ git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
+
+ if (entry && git_submodule_parse_ignore(
+ &diff->base.opts.ignore_submodules, entry->value) < 0)
+ giterr_clear();
+ git_config_entry_free(entry);
+ }
+
+ /* if either prefix is not set, figure out appropriate value */
+ if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) {
+ const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
+ const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
+
+ if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
+ use_old = use_new = "";
+ else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
+ use_old = diff_mnemonic_prefix(diff->base.old_src, true);
+ use_new = diff_mnemonic_prefix(diff->base.new_src, false);
+ }
+
+ if (!diff->base.opts.old_prefix)
+ diff->base.opts.old_prefix = use_old;
+ if (!diff->base.opts.new_prefix)
+ diff->base.opts.new_prefix = use_new;
+ }
+
+ /* strdup prefix from pool so we're not dependent on external data */
+ diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix);
+ diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix);
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ const char *tmp_prefix = diff->base.opts.old_prefix;
+ diff->base.opts.old_prefix = diff->base.opts.new_prefix;
+ diff->base.opts.new_prefix = tmp_prefix;
+ }
+
+ git_config_free(cfg);
+
+ /* check strdup results for error */
+ return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0;
+}
+
+int git_diff__oid_for_file(
+ git_oid *out,
+ git_diff *diff,
+ const char *path,
+ uint16_t mode,
+ git_off_t size)
+{
+ git_index_entry entry;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.mode = mode;
+ entry.file_size = size;
+ entry.path = (char *)path;
+
+ return git_diff__oid_for_entry(out, diff, &entry, mode, NULL);
+}
+
+int git_diff__oid_for_entry(
+ git_oid *out,
+ git_diff *d,
+ const git_index_entry *src,
+ uint16_t mode,
+ const git_oid *update_match)
+{
+ git_diff_generated *diff;
+ git_buf full_path = GIT_BUF_INIT;
+ git_index_entry entry = *src;
+ git_filter_list *fl = NULL;
+ int error = 0;
+
+ assert(d->type == GIT_DIFF_TYPE_GENERATED);
+ diff = (git_diff_generated *)d;
+
+ memset(out, 0, sizeof(*out));
+
+ if (git_buf_joinpath(&full_path,
+ git_repository_workdir(diff->base.repo), entry.path) < 0)
+ return -1;
+
+ if (!mode) {
+ struct stat st;
+
+ diff->base.perf.stat_calls++;
+
+ if (p_stat(full_path.ptr, &st) < 0) {
+ error = git_path_set_error(errno, entry.path, "stat");
+ git_buf_free(&full_path);
+ return error;
+ }
+
+ git_index_entry__init_from_stat(&entry,
+ &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
+ }
+
+ /* calculate OID for file if possible */
+ if (S_ISGITLINK(mode)) {
+ git_submodule *sm;
+
+ if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) {
+ const git_oid *sm_oid = git_submodule_wd_id(sm);
+ if (sm_oid)
+ git_oid_cpy(out, sm_oid);
+ git_submodule_free(sm);
+ } else {
+ /* if submodule lookup failed probably just in an intermediate
+ * state where some init hasn't happened, so ignore the error
+ */
+ giterr_clear();
+ }
+ } else if (S_ISLNK(mode)) {
+ error = git_odb__hashlink(out, full_path.ptr);
+ diff->base.perf.oid_calculations++;
+ } else if (!git__is_sizet(entry.file_size)) {
+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
+ entry.path);
+ error = -1;
+ } else if (!(error = git_filter_list_load(&fl,
+ diff->base.repo, NULL, entry.path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)))
+ {
+ int fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0)
+ error = fd;
+ else {
+ error = git_odb__hashfd_filtered(
+ out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
+ p_close(fd);
+ diff->base.perf.oid_calculations++;
+ }
+
+ git_filter_list_free(fl);
+ }
+
+ /* update index for entry if requested */
+ if (!error && update_match && git_oid_equal(out, update_match)) {
+ git_index *idx;
+ git_index_entry updated_entry;
+
+ memcpy(&updated_entry, &entry, sizeof(git_index_entry));
+ updated_entry.mode = mode;
+ git_oid_cpy(&updated_entry.id, out);
+
+ if (!(error = git_repository_index__weakptr(&idx,
+ diff->base.repo))) {
+ error = git_index_add(idx, &updated_entry);
+ diff->index_updated = true;
+ }
+ }
+
+ git_buf_free(&full_path);
+ return error;
+}
+
+typedef struct {
+ git_repository *repo;
+ git_iterator *old_iter;
+ git_iterator *new_iter;
+ const git_index_entry *oitem;
+ const git_index_entry *nitem;
+} diff_in_progress;
+
+#define MODE_BITS_MASK 0000777
+
+static int maybe_modified_submodule(
+ git_delta_t *status,
+ git_oid *found_oid,
+ git_diff_generated *diff,
+ diff_in_progress *info)
+{
+ int error = 0;
+ git_submodule *sub;
+ unsigned int sm_status = 0;
+ git_submodule_ignore_t ign = diff->base.opts.ignore_submodules;
+
+ *status = GIT_DELTA_UNMODIFIED;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
+ ign == GIT_SUBMODULE_IGNORE_ALL)
+ return 0;
+
+ if ((error = git_submodule_lookup(
+ &sub, diff->base.repo, info->nitem->path)) < 0) {
+
+ /* GIT_EEXISTS means dir with .git in it was found - ignore it */
+ if (error == GIT_EEXISTS) {
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
+ /* ignore it */;
+ else if ((error = git_submodule__status(
+ &sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
+ /* return error below */;
+
+ /* check IS_WD_UNMODIFIED because this case is only used
+ * when the new side of the diff is the working directory
+ */
+ else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
+ *status = GIT_DELTA_MODIFIED;
+
+ /* now that we have a HEAD OID, check if HEAD moved */
+ else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
+ !git_oid_equal(&info->oitem->id, found_oid))
+ *status = GIT_DELTA_MODIFIED;
+
+ git_submodule_free(sub);
+ return error;
+}
+
+static int maybe_modified(
+ git_diff_generated *diff,
+ diff_in_progress *info)
+{
+ git_oid noid;
+ git_delta_t status = GIT_DELTA_MODIFIED;
+ const git_index_entry *oitem = info->oitem;
+ const git_index_entry *nitem = info->nitem;
+ unsigned int omode = oitem->mode;
+ unsigned int nmode = nitem->mode;
+ bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
+ bool modified_uncertain = false;
+ const char *matched_pathspec;
+ int error = 0;
+
+ if (!diff_pathspec_match(&matched_pathspec, diff, oitem))
+ return 0;
+
+ memset(&noid, 0, sizeof(noid));
+
+ /* on platforms with no symlinks, preserve mode of existing symlinks */
+ if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
+ !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
+ nmode = omode;
+
+ /* on platforms with no execmode, just preserve old mode */
+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
+ (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
+ new_is_workdir)
+ nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
+
+ /* if one side is a conflict, mark the whole delta as conflicted */
+ if (git_index_entry_is_conflict(oitem) ||
+ git_index_entry_is_conflict(nitem)) {
+ status = GIT_DELTA_CONFLICTED;
+
+ /* support "assume unchanged" (poorly, b/c we still stat everything) */
+ } else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) {
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* support "skip worktree" index bit */
+ } else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) {
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if basic type of file changed, then split into delete and add */
+ } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) {
+ status = GIT_DELTA_TYPECHANGE;
+ }
+
+ else if (nmode == GIT_FILEMODE_UNREADABLE) {
+ if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
+ error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem);
+ return error;
+ }
+
+ else {
+ if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
+ error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
+ return error;
+ }
+
+ /* if oids and modes match (and are valid), then file is unmodified */
+ } else if (git_oid_equal(&oitem->id, &nitem->id) &&
+ omode == nmode &&
+ !git_oid_iszero(&oitem->id)) {
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if we have an unknown OID and a workdir iterator, then check some
+ * circumstances that can accelerate things or need special handling
+ */
+ } else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
+ bool use_ctime =
+ ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
+ git_index *index = git_iterator_index(info->new_iter);
+
+ status = GIT_DELTA_UNMODIFIED;
+
+ if (S_ISGITLINK(nmode)) {
+ if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
+ return error;
+ }
+
+ /* if the stat data looks different, then mark modified - this just
+ * means that the OID will be recalculated below to confirm change
+ */
+ else if (omode != nmode || oitem->file_size != nitem->file_size) {
+ status = GIT_DELTA_MODIFIED;
+ modified_uncertain =
+ (oitem->file_size <= 0 && nitem->file_size > 0);
+ }
+ else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) ||
+ (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) ||
+ oitem->ino != nitem->ino ||
+ oitem->uid != nitem->uid ||
+ oitem->gid != nitem->gid ||
+ git_index_entry_newer_than_index(nitem, index))
+ {
+ status = GIT_DELTA_MODIFIED;
+ modified_uncertain = true;
+ }
+
+ /* if mode is GITLINK and submodules are ignored, then skip */
+ } else if (S_ISGITLINK(nmode) &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) {
+ status = GIT_DELTA_UNMODIFIED;
+ }
+
+ /* if we got here and decided that the files are modified, but we
+ * haven't calculated the OID of the new item, then calculate it now
+ */
+ if (modified_uncertain && git_oid_iszero(&nitem->id)) {
+ const git_oid *update_check =
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ?
+ &oitem->id : NULL;
+
+ if ((error = git_diff__oid_for_entry(
+ &noid, &diff->base, nitem, nmode, update_check)) < 0)
+ return error;
+
+ /* if oid matches, then mark unmodified (except submodules, where
+ * the filesystem content may be modified even if the oid still
+ * matches between the index and the workdir HEAD)
+ */
+ if (omode == nmode && !S_ISGITLINK(omode) &&
+ git_oid_equal(&oitem->id, &noid))
+ status = GIT_DELTA_UNMODIFIED;
+ }
+
+ /* If we want case changes, then break this into a delete of the old
+ * and an add of the new so that consumers can act accordingly (eg,
+ * checkout will update the case on disk.)
+ */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) &&
+ strcmp(oitem->path, nitem->path) != 0) {
+
+ if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
+ error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
+
+ return error;
+ }
+
+ return diff_delta__from_two(
+ diff, status, oitem, omode, nitem, nmode,
+ git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
+}
+
+static bool entry_is_prefixed(
+ git_diff_generated *diff,
+ const git_index_entry *item,
+ const git_index_entry *prefix_item)
+{
+ size_t pathlen;
+
+ if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0)
+ return false;
+
+ pathlen = strlen(prefix_item->path);
+
+ return (prefix_item->path[pathlen - 1] == '/' ||
+ item->path[pathlen] == '\0' ||
+ item->path[pathlen] == '/');
+}
+
+static int iterator_current(
+ const git_index_entry **entry,
+ git_iterator *iterator)
+{
+ int error;
+
+ if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) {
+ *entry = NULL;
+ error = 0;
+ }
+
+ return error;
+}
+
+static int iterator_advance(
+ const git_index_entry **entry,
+ git_iterator *iterator)
+{
+ const git_index_entry *prev_entry = *entry;
+ int cmp, error;
+
+ /* if we're looking for conflicts, we only want to report
+ * one conflict for each file, instead of all three sides.
+ * so if this entry is a conflict for this file, and the
+ * previous one was a conflict for the same file, skip it.
+ */
+ while ((error = git_iterator_advance(entry, iterator)) == 0) {
+ if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) ||
+ !git_index_entry_is_conflict(prev_entry) ||
+ !git_index_entry_is_conflict(*entry))
+ break;
+
+ cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ?
+ strcasecmp(prev_entry->path, (*entry)->path) :
+ strcmp(prev_entry->path, (*entry)->path);
+
+ if (cmp)
+ break;
+ }
+
+ if (error == GIT_ITEROVER) {
+ *entry = NULL;
+ error = 0;
+ }
+
+ return error;
+}
+
+static int iterator_advance_into(
+ const git_index_entry **entry,
+ git_iterator *iterator)
+{
+ int error;
+
+ if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) {
+ *entry = NULL;
+ error = 0;
+ }
+
+ return error;
+}
+
+static int iterator_advance_over(
+ const git_index_entry **entry,
+ git_iterator_status_t *status,
+ git_iterator *iterator)
+{
+ int error = git_iterator_advance_over(entry, status, iterator);
+
+ if (error == GIT_ITEROVER) {
+ *entry = NULL;
+ error = 0;
+ }
+
+ return error;
+}
+
+static int handle_unmatched_new_item(
+ git_diff_generated *diff, diff_in_progress *info)
+{
+ int error = 0;
+ const git_index_entry *nitem = info->nitem;
+ git_delta_t delta_type = GIT_DELTA_UNTRACKED;
+ bool contains_oitem;
+
+ /* check if this is a prefix of the other side */
+ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
+
+ /* update delta_type if this item is conflicted */
+ if (git_index_entry_is_conflict(nitem))
+ delta_type = GIT_DELTA_CONFLICTED;
+
+ /* update delta_type if this item is ignored */
+ else if (git_iterator_current_is_ignored(info->new_iter))
+ delta_type = GIT_DELTA_IGNORED;
+
+ if (nitem->mode == GIT_FILEMODE_TREE) {
+ bool recurse_into_dir = contains_oitem;
+
+ /* check if user requests recursion into this type of dir */
+ recurse_into_dir = contains_oitem ||
+ (delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
+ (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
+
+ /* do not advance into directories that contain a .git file */
+ if (recurse_into_dir && !contains_oitem) {
+ git_buf *full = NULL;
+ if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
+ return -1;
+ if (full && git_path_contains(full, DOT_GIT)) {
+ /* TODO: warning if not a valid git repository */
+ recurse_into_dir = false;
+ }
+ }
+
+ /* still have to look into untracked directories to match core git -
+ * with no untracked files, directory is treated as ignored
+ */
+ if (!recurse_into_dir &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
+ {
+ git_diff_delta *last;
+ git_iterator_status_t untracked_state;
+
+ /* attempt to insert record for this directory */
+ if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
+ return error;
+
+ /* if delta wasn't created (because of rules), just skip ahead */
+ last = diff_delta__last_for_item(diff, nitem);
+ if (!last)
+ return iterator_advance(&info->nitem, info->new_iter);
+
+ /* iterate into dir looking for an actual untracked file */
+ if ((error = iterator_advance_over(
+ &info->nitem, &untracked_state, info->new_iter)) < 0)
+ return error;
+
+ /* if we found nothing that matched our pathlist filter, exclude */
+ if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) {
+ git_vector_pop(&diff->base.deltas);
+ git__free(last);
+ }
+
+ /* if we found nothing or just ignored items, update the record */
+ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
+ untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
+ last->status = GIT_DELTA_IGNORED;
+
+ /* remove the record if we don't want ignored records */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
+ git_vector_pop(&diff->base.deltas);
+ git__free(last);
+ }
+ }
+
+ return 0;
+ }
+
+ /* try to advance into directory if necessary */
+ if (recurse_into_dir) {
+ error = iterator_advance_into(&info->nitem, info->new_iter);
+
+ /* if directory is empty, can't advance into it, so skip it */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = iterator_advance(&info->nitem, info->new_iter);
+ }
+
+ return error;
+ }
+ }
+
+ else if (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
+ git_iterator_current_tree_is_ignored(info->new_iter))
+ /* item contained in ignored directory, so skip over it */
+ return iterator_advance(&info->nitem, info->new_iter);
+
+ else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) {
+ if (delta_type != GIT_DELTA_CONFLICTED)
+ delta_type = GIT_DELTA_ADDED;
+ }
+
+ else if (nitem->mode == GIT_FILEMODE_COMMIT) {
+ /* ignore things that are not actual submodules */
+ if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
+ giterr_clear();
+ delta_type = GIT_DELTA_IGNORED;
+
+ /* if this contains a tracked item, treat as normal TREE */
+ if (contains_oitem) {
+ error = iterator_advance_into(&info->nitem, info->new_iter);
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ giterr_clear();
+ return iterator_advance(&info->nitem, info->new_iter);
+ }
+ }
+ }
+
+ else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
+ delta_type = GIT_DELTA_UNTRACKED;
+ else
+ delta_type = GIT_DELTA_UNREADABLE;
+ }
+
+ /* Actually create the record for this item if necessary */
+ if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
+ return error;
+
+ /* If user requested TYPECHANGE records, then check for that instead of
+ * just generating an ADDED/UNTRACKED record
+ */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ contains_oitem)
+ {
+ /* this entry was prefixed with a tree - make TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->old_file.mode = GIT_FILEMODE_TREE;
+ }
+ }
+
+ return iterator_advance(&info->nitem, info->new_iter);
+}
+
+static int handle_unmatched_old_item(
+ git_diff_generated *diff, diff_in_progress *info)
+{
+ git_delta_t delta_type = GIT_DELTA_DELETED;
+ int error;
+
+ /* update delta_type if this item is conflicted */
+ if (git_index_entry_is_conflict(info->oitem))
+ delta_type = GIT_DELTA_CONFLICTED;
+
+ if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
+ return error;
+
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating a DELETE record
+ */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ entry_is_prefixed(diff, info->nitem, info->oitem))
+ {
+ /* this entry has become a tree! convert to TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->new_file.mode = GIT_FILEMODE_TREE;
+ }
+
+ /* If new_iter is a workdir iterator, then this situation
+ * will certainly be followed by a series of untracked items.
+ * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
+ */
+ if (S_ISDIR(info->nitem->mode) &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
+ return iterator_advance(&info->nitem, info->new_iter);
+ }
+
+ return iterator_advance(&info->oitem, info->old_iter);
+}
+
+static int handle_matched_item(
+ git_diff_generated *diff, diff_in_progress *info)
+{
+ int error = 0;
+
+ if ((error = maybe_modified(diff, info)) < 0)
+ return error;
+
+ if (!(error = iterator_advance(&info->oitem, info->old_iter)))
+ error = iterator_advance(&info->nitem, info->new_iter);
+
+ return error;
+}
+
+int git_diff__from_iterators(
+ git_diff **out,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts)
+{
+ git_diff_generated *diff;
+ diff_in_progress info;
+ int error = 0;
+
+ *out = NULL;
+
+ diff = diff_generated_alloc(repo, old_iter, new_iter);
+ GITERR_CHECK_ALLOC(diff);
+
+ info.repo = repo;
+ info.old_iter = old_iter;
+ info.new_iter = new_iter;
+
+ /* make iterators have matching icase behavior */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
+ git_iterator_set_ignore_case(old_iter, true);
+ git_iterator_set_ignore_case(new_iter, true);
+ }
+
+ /* finish initialization */
+ if ((error = diff_generated_apply_options(diff, opts)) < 0)
+ goto cleanup;
+
+ if ((error = iterator_current(&info.oitem, old_iter)) < 0 ||
+ (error = iterator_current(&info.nitem, new_iter)) < 0)
+ goto cleanup;
+
+ /* run iterators building diffs */
+ while (!error && (info.oitem || info.nitem)) {
+ int cmp;
+
+ /* report progress */
+ if (opts && opts->progress_cb) {
+ if ((error = opts->progress_cb(&diff->base,
+ info.oitem ? info.oitem->path : NULL,
+ info.nitem ? info.nitem->path : NULL,
+ opts->payload)))
+ break;
+ }
+
+ cmp = info.oitem ?
+ (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1;
+
+ /* create DELETED records for old items not matched in new */
+ if (cmp < 0)
+ error = handle_unmatched_old_item(diff, &info);
+
+ /* create ADDED, TRACKED, or IGNORED records for new items not
+ * matched in old (and/or descend into directories as needed)
+ */
+ else if (cmp > 0)
+ error = handle_unmatched_new_item(diff, &info);
+
+ /* otherwise item paths match, so create MODIFIED record
+ * (or ADDED and DELETED pair if type changed)
+ */
+ else
+ error = handle_matched_item(diff, &info);
+ }
+
+ diff->base.perf.stat_calls +=
+ old_iter->stat_calls + new_iter->stat_calls;
+
+cleanup:
+ if (!error)
+ *out = &diff->base;
+ else
+ git_diff_free(&diff->base);
+
+ return error;
+}
+
+#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
+ git_iterator *a = NULL, *b = NULL; \
+ char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \
+ git_pathspec_prefix(&opts->pathspec) : NULL; \
+ git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
+ b_opts = GIT_ITERATOR_OPTIONS_INIT; \
+ a_opts.flags = FLAGS_FIRST; \
+ a_opts.start = pfx; \
+ a_opts.end = pfx; \
+ b_opts.flags = FLAGS_SECOND; \
+ b_opts.start = pfx; \
+ b_opts.end = pfx; \
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
+ if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \
+ a_opts.pathlist.strings = opts->pathspec.strings; \
+ a_opts.pathlist.count = opts->pathspec.count; \
+ b_opts.pathlist.strings = opts->pathspec.strings; \
+ b_opts.pathlist.count = opts->pathspec.count; \
+ } \
+ if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ error = git_diff__from_iterators(&diff, repo, a, b, opts); \
+ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
+} while (0)
+
+int git_diff_tree_to_tree(
+ git_diff **out,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_tree *new_tree,
+ const git_diff_options *opts)
+{
+ git_diff *diff = NULL;
+ git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ /* for tree to tree diff, be case sensitive even if the index is
+ * currently case insensitive, unless the user explicitly asked
+ * for case insensitivity
+ */
+ if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
+ iflag = GIT_ITERATOR_IGNORE_CASE;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
+ git_iterator_for_tree(&b, new_tree, &b_opts), iflag
+ );
+
+ if (!error)
+ *out = diff;
+
+ return error;
+}
+
+static int diff_load_index(git_index **index, git_repository *repo)
+{
+ int error = git_repository_index__weakptr(index, repo);
+
+ /* reload the repository index when user did not pass one in */
+ if (!error && git_index_read(*index, false) < 0)
+ giterr_clear();
+
+ return error;
+}
+
+int git_diff_tree_to_index(
+ git_diff **out,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_index *index,
+ const git_diff_options *opts)
+{
+ git_diff *diff = NULL;
+ git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_CONFLICTS;
+ bool index_ignore_case = false;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (!index && (error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ index_ignore_case = index->ignore_case;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
+ git_iterator_for_index(&b, repo, index, &b_opts), iflag
+ );
+
+ /* if index is in case-insensitive order, re-sort deltas to match */
+ if (!error && index_ignore_case)
+ git_diff__set_ignore_case(diff, true);
+
+ if (!error)
+ *out = diff;
+
+ return error;
+}
+
+int git_diff_index_to_workdir(
+ git_diff **out,
+ git_repository *repo,
+ git_index *index,
+ const git_diff_options *opts)
+{
+ git_diff *diff = NULL;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (!index && (error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index(&a, repo, index, &a_opts),
+ GIT_ITERATOR_INCLUDE_CONFLICTS,
+
+ git_iterator_for_workdir(&b, repo, index, NULL, &b_opts),
+ GIT_ITERATOR_DONT_AUTOEXPAND
+ );
+
+ if (!error && (diff->opts.flags & GIT_DIFF_UPDATE_INDEX) != 0 &&
+ ((git_diff_generated *)diff)->index_updated)
+ error = git_index_write(index);
+
+ if (!error)
+ *out = diff;
+
+ return error;
+}
+
+int git_diff_tree_to_workdir(
+ git_diff **out,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts)
+{
+ git_diff *diff = NULL;
+ git_index *index;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if ((error = git_repository_index__weakptr(&index, repo)))
+ return error;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, &a_opts), 0,
+ git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND
+ );
+
+ if (!error)
+ *out = diff;
+
+ return error;
+}
+
+int git_diff_tree_to_workdir_with_index(
+ git_diff **out,
+ git_repository *repo,
+ git_tree *tree,
+ const git_diff_options *opts)
+{
+ git_diff *d1 = NULL, *d2 = NULL;
+ git_index *index = NULL;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if ((error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) &&
+ !(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
+ error = git_diff_merge(d1, d2);
+
+ git_diff_free(d2);
+
+ if (error) {
+ git_diff_free(d1);
+ d1 = NULL;
+ }
+
+ *out = d1;
+ return error;
+}
+
+int git_diff_index_to_index(
+ git_diff **out,
+ git_repository *repo,
+ git_index *old_index,
+ git_index *new_index,
+ const git_diff_options *opts)
+{
+ git_diff *diff;
+ int error = 0;
+
+ assert(out && old_index && new_index);
+
+ *out = NULL;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE,
+ git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE
+ );
+
+ /* if index is in case-insensitive order, re-sort deltas to match */
+ if (!error && (old_index->ignore_case || new_index->ignore_case))
+ git_diff__set_ignore_case(diff, true);
+
+ if (!error)
+ *out = diff;
+
+ return error;
+}
+
+int git_diff__paired_foreach(
+ git_diff *head2idx,
+ git_diff *idx2wd,
+ int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
+ void *payload)
+{
+ int cmp, error = 0;
+ git_diff_delta *h2i, *i2w;
+ size_t i, j, i_max, j_max;
+ int (*strcomp)(const char *, const char *) = git__strcmp;
+ bool h2i_icase, i2w_icase, icase_mismatch;
+
+ i_max = head2idx ? head2idx->deltas.length : 0;
+ j_max = idx2wd ? idx2wd->deltas.length : 0;
+ if (!i_max && !j_max)
+ return 0;
+
+ /* At some point, tree-to-index diffs will probably never ignore case,
+ * even if that isn't true now. Index-to-workdir diffs may or may not
+ * ignore case, but the index filename for the idx2wd diff should
+ * still be using the canonical case-preserving name.
+ *
+ * Therefore the main thing we need to do here is make sure the diffs
+ * are traversed in a compatible order. To do this, we temporarily
+ * resort a mismatched diff to get the order correct.
+ *
+ * In order to traverse renames in the index->workdir, we need to
+ * ensure that we compare the index name on both sides, so we
+ * always sort by the old name in the i2w list.
+ */
+ h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx);
+ i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd);
+
+ icase_mismatch =
+ (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
+
+ if (icase_mismatch && h2i_icase) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
+ git_vector_sort(&head2idx->deltas);
+ }
+
+ if (i2w_icase && !icase_mismatch) {
+ strcomp = git__strcasecmp;
+
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
+ git_vector_sort(&idx2wd->deltas);
+ } else if (idx2wd != NULL) {
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
+ h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
+ i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
+
+ cmp = !i2w ? -1 : !h2i ? 1 :
+ strcomp(h2i->new_file.path, i2w->old_file.path);
+
+ if (cmp < 0) {
+ i++; i2w = NULL;
+ } else if (cmp > 0) {
+ j++; h2i = NULL;
+ } else {
+ i++; j++;
+ }
+
+ if ((error = cb(h2i, i2w, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ /* restore case-insensitive delta sort */
+ if (icase_mismatch && h2i_icase) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
+ git_vector_sort(&head2idx->deltas);
+ }
+
+ /* restore idx2wd sort by new path */
+ if (idx2wd != NULL) {
+ git_vector_set_cmp(&idx2wd->deltas,
+ i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+
+ return error;
+}
+
+int git_diff__commit(
+ git_diff **out,
+ git_repository *repo,
+ const git_commit *commit,
+ const git_diff_options *opts)
+{
+ git_commit *parent = NULL;
+ git_diff *commit_diff = NULL;
+ git_tree *old_tree = NULL, *new_tree = NULL;
+ size_t parents;
+ int error = 0;
+
+ *out = NULL;
+
+ if ((parents = git_commit_parentcount(commit)) > 1) {
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ error = -1;
+ giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
+ git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
+ goto on_error;
+ }
+
+ if (parents > 0)
+ if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
+ (error = git_commit_tree(&old_tree, parent)) < 0)
+ goto on_error;
+
+ if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
+ (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
+ goto on_error;
+
+ *out = commit_diff;
+
+on_error:
+ git_tree_free(new_tree);
+ git_tree_free(old_tree);
+ git_commit_free(parent);
+
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_generate_h__
+#define INCLUDE_diff_generate_h__
+
+enum {
+ GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
+ GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
+ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
+ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
+ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
+};
+
+#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
+#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
+
+enum {
+ GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
+ GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
+ GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
+ GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
+ GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
+ GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
+
+ GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
+ GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
+ GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
+ GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
+};
+
+#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
+
+#define GIT_DIFF__VERBOSE (1 << 30)
+
+extern void git_diff_addref(git_diff *diff);
+
+extern bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta);
+
+extern int git_diff__from_iterators(
+ git_diff **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts);
+
+extern int git_diff__commit(
+ git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
+
+extern int git_diff__paired_foreach(
+ git_diff *idx2head,
+ git_diff *wd2idx,
+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ void *payload);
+
+/* Merge two `git_diff`s according to the callback given by `cb`. */
+
+typedef git_diff_delta *(*git_diff__merge_cb)(
+ const git_diff_delta *left,
+ const git_diff_delta *right,
+ git_pool *pool);
+
+extern int git_diff__merge(
+ git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
+
+extern git_diff_delta *git_diff__merge_like_cgit(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool);
+
+/* Duplicate a `git_diff_delta` out of the `git_pool` */
+extern git_diff_delta *git_diff__delta_dup(
+ const git_diff_delta *d, git_pool *pool);
+
+extern int git_diff__oid_for_file(
+ git_oid *out,
+ git_diff *diff,
+ const char *path,
+ uint16_t mode,
+ git_off_t size);
+
+extern int git_diff__oid_for_entry(
+ git_oid *out,
+ git_diff *diff,
+ const git_index_entry *src,
+ uint16_t mode,
+ const git_oid *update_match);
+
+/*
+ * Sometimes a git_diff_file will have a zero size; this attempts to
+ * fill in the size without loading the blob if possible. If that is
+ * not possible, then it will return the git_odb_object that had to be
+ * loaded and the caller can use it or dispose of it as needed.
+ */
+GIT_INLINE(int) git_diff_file__resolve_zero_size(
+ git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
+{
+ int error;
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ error = git_odb__read_header_or_object(
+ odb_obj, &len, &type, odb, &file->id);
+
+ git_odb_free(odb);
+
+ if (!error)
+ file->size = (git_off_t)len;
+
+ return error;
+}
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "diff.h"
+#include "diff_parse.h"
+#include "patch.h"
+#include "patch_parse.h"
+
+static void diff_parsed_free(git_diff *d)
+{
+ git_diff_parsed *diff = (git_diff_parsed *)d;
+ git_patch *patch;
+ size_t i;
+
+ git_vector_foreach(&diff->patches, i, patch)
+ git_patch_free(patch);
+
+ git_vector_free(&diff->patches);
+
+ git_vector_free(&diff->base.deltas);
+ git_pool_clear(&diff->base.pool);
+
+ git__memzero(diff, sizeof(*diff));
+ git__free(diff);
+}
+
+static git_diff_parsed *diff_parsed_alloc(void)
+{
+ git_diff_parsed *diff;
+
+ if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL)
+ return NULL;
+
+ GIT_REFCOUNT_INC(diff);
+ diff->base.type = GIT_DIFF_TYPE_PARSED;
+ diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE;
+ diff->base.strcomp = git__strcmp;
+ diff->base.strncomp = git__strncmp;
+ diff->base.pfxcomp = git__prefixcmp;
+ diff->base.entrycomp = git_diff__entry_cmp;
+ diff->base.patch_fn = git_patch_parsed_from_diff;
+ diff->base.free_fn = diff_parsed_free;
+
+ git_pool_init(&diff->base.pool, 1);
+
+ if (git_vector_init(&diff->patches, 0, NULL) < 0 ||
+ git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
+ git_diff_free(&diff->base);
+ return NULL;
+ }
+
+ git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp);
+
+ return diff;
+}
+
+int git_diff_from_buffer(
+ git_diff **out,
+ const char *content,
+ size_t content_len)
+{
+ git_diff_parsed *diff;
+ git_patch *patch;
+ git_patch_parse_ctx *ctx = NULL;
+ int error = 0;
+
+ *out = NULL;
+
+ diff = diff_parsed_alloc();
+ GITERR_CHECK_ALLOC(diff);
+
+ ctx = git_patch_parse_ctx_init(content, content_len, NULL);
+ GITERR_CHECK_ALLOC(ctx);
+
+ while (ctx->remain_len) {
+ if ((error = git_patch_parse(&patch, ctx)) < 0)
+ break;
+
+ git_vector_insert(&diff->patches, patch);
+ git_vector_insert(&diff->base.deltas, patch->delta);
+ }
+
+ if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) {
+ giterr_clear();
+ error = 0;
+ }
+
+ git_patch_parse_ctx_free(ctx);
+
+ if (error < 0)
+ git_diff_free(&diff->base);
+ else
+ *out = &diff->base;
+
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_parse_h__
+#define INCLUDE_diff_parse_h__
+
+#include "diff.h"
+
+typedef struct {
+ struct git_diff base;
+
+ git_vector patches;
+} git_diff_parsed;
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "patch_generate.h"
+#include "fileops.h"
+#include "zstream.h"
+#include "blob.h"
+#include "delta.h"
+#include "git2/sys/diff.h"
+
+typedef struct {
+ git_diff_format_t format;
+ git_diff_line_cb print_cb;
+ void *payload;
+
+ git_buf *buf;
+ git_diff_line line;
+
+ const char *old_prefix;
+ const char *new_prefix;
+ uint32_t flags;
+ int id_strlen;
+
+ int (*strcomp)(const char *, const char *);
+} diff_print_info;
+
+static int diff_print_info_init__common(
+ diff_print_info *pi,
+ git_buf *out,
+ git_repository *repo,
+ git_diff_format_t format,
+ git_diff_line_cb cb,
+ void *payload)
+{
+ pi->format = format;
+ pi->print_cb = cb;
+ pi->payload = payload;
+ pi->buf = out;
+
+ if (!pi->id_strlen) {
+ if (!repo)
+ pi->id_strlen = GIT_ABBREV_DEFAULT;
+ else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0)
+ return -1;
+ }
+
+ if (pi->id_strlen > GIT_OID_HEXSZ)
+ pi->id_strlen = GIT_OID_HEXSZ;
+
+ memset(&pi->line, 0, sizeof(pi->line));
+ pi->line.old_lineno = -1;
+ pi->line.new_lineno = -1;
+ pi->line.num_lines = 1;
+
+ return 0;
+}
+
+static int diff_print_info_init_fromdiff(
+ diff_print_info *pi,
+ git_buf *out,
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb cb,
+ void *payload)
+{
+ git_repository *repo = diff ? diff->repo : NULL;
+
+ memset(pi, 0, sizeof(diff_print_info));
+
+ if (diff) {
+ pi->flags = diff->opts.flags;
+ pi->id_strlen = diff->opts.id_abbrev;
+ pi->old_prefix = diff->opts.old_prefix;
+ pi->new_prefix = diff->opts.new_prefix;
+
+ pi->strcomp = diff->strcomp;
+ }
+
+ return diff_print_info_init__common(pi, out, repo, format, cb, payload);
+}
+
+static int diff_print_info_init_frompatch(
+ diff_print_info *pi,
+ git_buf *out,
+ git_patch *patch,
+ git_diff_format_t format,
+ git_diff_line_cb cb,
+ void *payload)
+{
+ assert(patch);
+
+ memset(pi, 0, sizeof(diff_print_info));
+
+ pi->flags = patch->diff_opts.flags;
+ pi->id_strlen = patch->diff_opts.id_abbrev;
+ pi->old_prefix = patch->diff_opts.old_prefix;
+ pi->new_prefix = patch->diff_opts.new_prefix;
+
+ return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
+}
+
+static char diff_pick_suffix(int mode)
+{
+ if (S_ISDIR(mode))
+ return '/';
+ else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
+ /* in git, modes are very regular, so we must have 0100755 mode */
+ return '*';
+ else
+ return ' ';
+}
+
+char git_diff_status_char(git_delta_t status)
+{
+ char code;
+
+ switch (status) {
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ case GIT_DELTA_UNREADABLE: code = 'X'; break;
+ default: code = ' '; break;
+ }
+
+ return code;
+}
+
+static int diff_print_one_name_only(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
+ delta->status == GIT_DELTA_UNMODIFIED)
+ return 0;
+
+ git_buf_clear(out);
+ git_buf_puts(out, delta->new_file.path);
+ git_buf_putc(out, '\n');
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_one_name_status(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
+ int(*strcomp)(const char *, const char *) = pi->strcomp ?
+ pi->strcomp : git__strcmp;
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
+ return 0;
+
+ old_suffix = diff_pick_suffix(delta->old_file.mode);
+ new_suffix = diff_pick_suffix(delta->new_file.mode);
+
+ git_buf_clear(out);
+
+ if (delta->old_file.path != delta->new_file.path &&
+ strcomp(delta->old_file.path,delta->new_file.path) != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (delta->old_file.mode != delta->new_file.mode &&
+ delta->old_file.mode != 0 && delta->new_file.mode != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (old_suffix != ' ')
+ git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
+ else
+ git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_one_raw(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ int id_abbrev;
+ char code = git_diff_status_char(delta->status);
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
+ return 0;
+
+ git_buf_clear(out);
+
+ id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
+ delta->new_file.id_abbrev;
+
+ if (pi->id_strlen > id_abbrev) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ id_abbrev, pi->id_strlen);
+ return -1;
+ }
+
+ git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
+ git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
+
+ git_buf_printf(
+ out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
+ ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
+ delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
+
+ if (delta->similarity > 0)
+ git_buf_printf(out, "%03u", delta->similarity);
+
+ if (delta->old_file.path != delta->new_file.path)
+ git_buf_printf(
+ out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
+ else
+ git_buf_printf(
+ out, "\t%s\n", delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_modes(
+ git_buf *out, const git_diff_delta *delta)
+{
+ git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int diff_print_oid_range(
+ git_buf *out, const git_diff_delta *delta, int id_strlen)
+{
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ if (delta->old_file.mode &&
+ id_strlen > delta->old_file.id_abbrev) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ delta->old_file.id_abbrev, id_strlen);
+ return -1;
+ }
+
+ if ((delta->new_file.mode &&
+ id_strlen > delta->new_file.id_abbrev)) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ delta->new_file.id_abbrev, id_strlen);
+ return -1;
+ }
+
+ git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
+ git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
+
+ if (delta->old_file.mode == delta->new_file.mode) {
+ git_buf_printf(out, "index %s..%s %o\n",
+ start_oid, end_oid, delta->old_file.mode);
+ } else {
+ if (delta->old_file.mode == 0)
+ git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
+ else if (delta->new_file.mode == 0)
+ git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
+ else
+ diff_print_modes(out, delta);
+
+ git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
+ }
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int diff_delta_format_path(
+ git_buf *out, const char *prefix, const char *filename)
+{
+ if (git_buf_joinpath(out, prefix, filename) < 0)
+ return -1;
+
+ return git_buf_quote(out);
+}
+
+static int diff_delta_format_with_paths(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *template,
+ const char *oldpath,
+ const char *newpath)
+{
+ if (git_oid_iszero(&delta->old_file.id))
+ oldpath = "/dev/null";
+
+ if (git_oid_iszero(&delta->new_file.id))
+ newpath = "/dev/null";
+
+ return git_buf_printf(out, template, oldpath, newpath);
+}
+
+int diff_delta_format_similarity_header(
+ git_buf *out,
+ const git_diff_delta *delta)
+{
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+ const char *type;
+ int error = 0;
+
+ if (delta->similarity > 100) {
+ giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity);
+ error = -1;
+ goto done;
+ }
+
+ if (delta->status == GIT_DELTA_RENAMED)
+ type = "rename";
+ else if (delta->status == GIT_DELTA_COPIED)
+ type = "copy";
+ else
+ abort();
+
+ if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
+ (error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
+ (error = git_buf_quote(&old_path)) < 0 ||
+ (error = git_buf_quote(&new_path)) < 0)
+ goto done;
+
+ git_buf_printf(out,
+ "similarity index %d%%\n"
+ "%s from %s\n"
+ "%s to %s\n",
+ delta->similarity,
+ type, old_path.ptr,
+ type, new_path.ptr);
+
+ if (git_buf_oom(out))
+ error = -1;
+
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+
+ return error;
+}
+
+static bool delta_is_unchanged(const git_diff_delta *delta)
+{
+ if (git_oid_iszero(&delta->old_file.id) &&
+ git_oid_iszero(&delta->new_file.id))
+ return true;
+
+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT)
+ return false;
+
+ if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
+ return true;
+
+ return false;
+}
+
+int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int id_strlen)
+{
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+ bool unchanged = delta_is_unchanged(delta);
+ int error = 0;
+
+ if (!oldpfx)
+ oldpfx = DIFF_OLD_PREFIX_DEFAULT;
+ if (!newpfx)
+ newpfx = DIFF_NEW_PREFIX_DEFAULT;
+ if (!id_strlen)
+ id_strlen = GIT_ABBREV_DEFAULT;
+
+ if ((error = diff_delta_format_path(
+ &old_path, oldpfx, delta->old_file.path)) < 0 ||
+ (error = diff_delta_format_path(
+ &new_path, newpfx, delta->new_file.path)) < 0)
+ goto done;
+
+ git_buf_clear(out);
+
+ git_buf_printf(out, "diff --git %s %s\n",
+ old_path.ptr, new_path.ptr);
+
+ if (delta->status == GIT_DELTA_RENAMED ||
+ (delta->status == GIT_DELTA_COPIED && unchanged)) {
+ if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
+ goto done;
+ }
+
+ if (!unchanged) {
+ if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0)
+ goto done;
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ diff_delta_format_with_paths(out, delta,
+ "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
+ }
+
+ if (unchanged && delta->old_file.mode != delta->new_file.mode)
+ diff_print_modes(out, delta);
+
+ if (git_buf_oom(out))
+ error = -1;
+
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+
+ return error;
+}
+
+static int format_binary(
+ diff_print_info *pi,
+ git_diff_binary_t type,
+ const char *data,
+ size_t datalen,
+ size_t inflatedlen)
+{
+ const char *typename = type == GIT_DIFF_BINARY_DELTA ?
+ "delta" : "literal";
+ const char *scan, *end;
+
+ git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen);
+ pi->line.num_lines++;
+
+ for (scan = data, end = data + datalen; scan < end; ) {
+ size_t chunk_len = end - scan;
+ if (chunk_len > 52)
+ chunk_len = 52;
+
+ if (chunk_len <= 26)
+ git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
+ else
+ git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
+
+ git_buf_encode_base85(pi->buf, scan, chunk_len);
+ git_buf_putc(pi->buf, '\n');
+
+ if (git_buf_oom(pi->buf))
+ return -1;
+
+ scan += chunk_len;
+ pi->line.num_lines++;
+ }
+ git_buf_putc(pi->buf, '\n');
+
+ return 0;
+}
+
+static int diff_print_patch_file_binary_noshow(
+ diff_print_info *pi, git_diff_delta *delta,
+ const char *old_pfx, const char *new_pfx)
+{
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+ int error;
+
+ if ((error = diff_delta_format_path(
+ &old_path, old_pfx, delta->old_file.path)) < 0 ||
+ (error = diff_delta_format_path(
+ &new_path, new_pfx, delta->new_file.path)) < 0)
+ goto done;
+
+ pi->line.num_lines = 1;
+ error = diff_delta_format_with_paths(
+ pi->buf, delta, "Binary files %s and %s differ\n",
+ old_path.ptr, new_path.ptr);
+
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+
+ return error;
+}
+
+static int diff_print_patch_file_binary(
+ diff_print_info *pi, git_diff_delta *delta,
+ const char *old_pfx, const char *new_pfx,
+ const git_diff_binary *binary)
+{
+ size_t pre_binary_size;
+ int error;
+
+ if (delta->status == GIT_DELTA_UNMODIFIED)
+ return 0;
+
+ if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data)
+ return diff_print_patch_file_binary_noshow(
+ pi, delta, old_pfx, new_pfx);
+
+ pre_binary_size = pi->buf->size;
+ git_buf_printf(pi->buf, "GIT binary patch\n");
+ pi->line.num_lines++;
+
+ if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data,
+ binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
+ (error = format_binary(pi, binary->old_file.type, binary->old_file.data,
+ binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {
+
+ if (error == GIT_EBUFS) {
+ giterr_clear();
+ git_buf_truncate(pi->buf, pre_binary_size);
+
+ return diff_print_patch_file_binary_noshow(
+ pi, delta, old_pfx, new_pfx);
+ }
+ }
+
+ pi->line.num_lines++;
+ return error;
+}
+
+static int diff_print_patch_file(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ int error;
+ diff_print_info *pi = data;
+ const char *oldpfx =
+ pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ const char *newpfx =
+ pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+
+ bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
+ (pi->flags & GIT_DIFF_FORCE_BINARY);
+ bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
+ int id_strlen = pi->id_strlen;
+
+ if (binary && show_binary)
+ id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev :
+ delta->new_file.id_abbrev;
+
+ GIT_UNUSED(progress);
+
+ if (S_ISDIR(delta->new_file.mode) ||
+ delta->status == GIT_DELTA_UNMODIFIED ||
+ delta->status == GIT_DELTA_IGNORED ||
+ delta->status == GIT_DELTA_UNREADABLE ||
+ (delta->status == GIT_DELTA_UNTRACKED &&
+ (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
+ return 0;
+
+ if ((error = git_diff_delta__format_file_header(
+ pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0)
+ return error;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(pi->buf);
+ pi->line.content_len = git_buf_len(pi->buf);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_patch_binary(
+ const git_diff_delta *delta,
+ const git_diff_binary *binary,
+ void *data)
+{
+ diff_print_info *pi = data;
+ const char *old_pfx =
+ pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ const char *new_pfx =
+ pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+ int error;
+
+ git_buf_clear(pi->buf);
+
+ if ((error = diff_print_patch_file_binary(
+ pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
+ return error;
+
+ pi->line.origin = GIT_DIFF_LINE_BINARY;
+ pi->line.content = git_buf_cstr(pi->buf);
+ pi->line.content_len = git_buf_len(pi->buf);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_patch_hunk(
+ const git_diff_delta *d,
+ const git_diff_hunk *h,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(d->new_file.mode))
+ return 0;
+
+ pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
+ pi->line.content = h->header;
+ pi->line.content_len = h->header_len;
+
+ return pi->print_cb(d, h, &pi->line, pi->payload);
+}
+
+static int diff_print_patch_line(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(delta->new_file.mode))
+ return 0;
+
+ return pi->print_cb(delta, hunk, line, pi->payload);
+}
+
+/* print a git_diff to an output callback */
+int git_diff_print(
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+ git_diff_file_cb print_file = NULL;
+ git_diff_binary_cb print_binary = NULL;
+ git_diff_hunk_cb print_hunk = NULL;
+ git_diff_line_cb print_line = NULL;
+
+ switch (format) {
+ case GIT_DIFF_FORMAT_PATCH:
+ print_file = diff_print_patch_file;
+ print_binary = diff_print_patch_binary;
+ print_hunk = diff_print_patch_hunk;
+ print_line = diff_print_patch_line;
+ break;
+ case GIT_DIFF_FORMAT_PATCH_HEADER:
+ print_file = diff_print_patch_file;
+ break;
+ case GIT_DIFF_FORMAT_RAW:
+ print_file = diff_print_one_raw;
+ break;
+ case GIT_DIFF_FORMAT_NAME_ONLY:
+ print_file = diff_print_one_name_only;
+ break;
+ case GIT_DIFF_FORMAT_NAME_STATUS:
+ print_file = diff_print_one_name_status;
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
+ return -1;
+ }
+
+ if (!(error = diff_print_info_init_fromdiff(
+ &pi, &buf, diff, format, print_cb, payload))) {
+ error = git_diff_foreach(
+ diff, print_file, print_binary, print_hunk, print_line, &pi);
+
+ if (error) /* make sure error message is set */
+ giterr_set_after_callback_function(error, "git_diff_print");
+ }
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+int git_diff_print_callback__to_buf(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ git_buf *output = payload;
+ GIT_UNUSED(delta); GIT_UNUSED(hunk);
+
+ if (!output) {
+ giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
+ return -1;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION ||
+ line->origin == GIT_DIFF_LINE_CONTEXT)
+ git_buf_putc(output, line->origin);
+
+ return git_buf_put(output, line->content, line->content_len);
+}
+
+int git_diff_print_callback__to_file_handle(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ FILE *fp = payload ? payload : stdout;
+
+ GIT_UNUSED(delta); GIT_UNUSED(hunk);
+
+ if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+ line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION)
+ fputc(line->origin, fp);
+ fwrite(line->content, 1, line->content_len, fp);
+ return 0;
+}
+
+/* print a git_diff to a git_buf */
+int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
+{
+ assert(out && diff);
+ git_buf_sanitize(out);
+ return git_diff_print(
+ diff, format, git_diff_print_callback__to_buf, out);
+}
+
+/* print a git_patch to an output callback */
+int git_patch_print(
+ git_patch *patch,
+ git_diff_line_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ assert(patch && print_cb);
+
+ if (!(error = diff_print_info_init_frompatch(
+ &pi, &temp, patch,
+ GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
+ {
+ error = git_patch__invoke_callbacks(
+ patch,
+ diff_print_patch_file, diff_print_patch_binary,
+ diff_print_patch_hunk, diff_print_patch_line,
+ &pi);
+
+ if (error) /* make sure error message is set */
+ giterr_set_after_callback_function(error, "git_patch_print");
+ }
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
+/* print a git_patch to a git_buf */
+int git_patch_to_buf(git_buf *out, git_patch *patch)
+{
+ assert(out && patch);
+ git_buf_sanitize(out);
+ return git_patch_print(patch, git_diff_print_callback__to_buf, out);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "vector.h"
+#include "diff.h"
+#include "patch_generate.h"
+
+#define DIFF_RENAME_FILE_SEPARATOR " => "
+#define STATS_FULL_MIN_SCALE 7
+
+typedef struct {
+ size_t insertions;
+ size_t deletions;
+} diff_file_stats;
+
+struct git_diff_stats {
+ git_diff *diff;
+ diff_file_stats *filestats;
+
+ size_t files_changed;
+ size_t insertions;
+ size_t deletions;
+ size_t renames;
+
+ size_t max_name;
+ size_t max_filestat;
+ int max_digits;
+};
+
+static int digits_for_value(size_t val)
+{
+ int count = 1;
+ size_t placevalue = 10;
+
+ while (val >= placevalue) {
+ ++count;
+ placevalue *= 10;
+ }
+
+ return count;
+}
+
+int git_diff_file_stats__full_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const diff_file_stats *filestat,
+ const git_diff_stats *stats,
+ size_t width)
+{
+ const char *old_path = NULL, *new_path = NULL;
+ size_t padding, old_size, new_size;
+
+ old_path = delta->old_file.path;
+ new_path = delta->new_file.path;
+ old_size = delta->old_file.size;
+ new_size = delta->new_file.size;
+
+ if (git_buf_printf(out, " %s", old_path) < 0)
+ goto on_error;
+
+ if (strcmp(old_path, new_path) != 0) {
+ padding = stats->max_name - strlen(old_path) - strlen(new_path);
+
+ if (git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path) < 0)
+ goto on_error;
+ } else {
+ padding = stats->max_name - strlen(old_path);
+
+ if (stats->renames > 0)
+ padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
+ }
+
+ if (git_buf_putcn(out, ' ', padding) < 0 ||
+ git_buf_puts(out, " | ") < 0)
+ goto on_error;
+
+ if (delta->flags & GIT_DIFF_FLAG_BINARY) {
+ if (git_buf_printf(out,
+ "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size) < 0)
+ goto on_error;
+ }
+ else {
+ if (git_buf_printf(out,
+ "%*" PRIuZ, stats->max_digits,
+ filestat->insertions + filestat->deletions) < 0)
+ goto on_error;
+
+ if (filestat->insertions || filestat->deletions) {
+ if (git_buf_putc(out, ' ') < 0)
+ goto on_error;
+
+ if (!width) {
+ if (git_buf_putcn(out, '+', filestat->insertions) < 0 ||
+ git_buf_putcn(out, '-', filestat->deletions) < 0)
+ goto on_error;
+ } else {
+ size_t total = filestat->insertions + filestat->deletions;
+ size_t full = (total * width + stats->max_filestat / 2) /
+ stats->max_filestat;
+ size_t plus = full * filestat->insertions / total;
+ size_t minus = full - plus;
+
+ if (git_buf_putcn(out, '+', max(plus, 1)) < 0 ||
+ git_buf_putcn(out, '-', max(minus, 1)) < 0)
+ goto on_error;
+ }
+ }
+ }
+
+ git_buf_putc(out, '\n');
+
+on_error:
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_diff_file_stats__number_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const diff_file_stats *filestats)
+{
+ int error;
+ const char *path = delta->new_file.path;
+
+ if (delta->flags & GIT_DIFF_FLAG_BINARY)
+ error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
+ else
+ error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n",
+ filestats->insertions, filestats->deletions, path);
+
+ return error;
+}
+
+int git_diff_file_stats__summary_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta)
+{
+ if (delta->old_file.mode != delta->new_file.mode) {
+ if (delta->old_file.mode == 0) {
+ git_buf_printf(out, " create mode %06o %s\n",
+ delta->new_file.mode, delta->new_file.path);
+ }
+ else if (delta->new_file.mode == 0) {
+ git_buf_printf(out, " delete mode %06o %s\n",
+ delta->old_file.mode, delta->old_file.path);
+ }
+ else {
+ git_buf_printf(out, " mode change %06o => %06o %s\n",
+ delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+int git_diff_get_stats(
+ git_diff_stats **out,
+ git_diff *diff)
+{
+ size_t i, deltas;
+ size_t total_insertions = 0, total_deletions = 0;
+ git_diff_stats *stats = NULL;
+ int error = 0;
+
+ assert(out && diff);
+
+ stats = git__calloc(1, sizeof(git_diff_stats));
+ GITERR_CHECK_ALLOC(stats);
+
+ deltas = git_diff_num_deltas(diff);
+
+ stats->filestats = git__calloc(deltas, sizeof(diff_file_stats));
+ if (!stats->filestats) {
+ git__free(stats);
+ return -1;
+ }
+
+ stats->diff = diff;
+ GIT_REFCOUNT_INC(diff);
+
+ for (i = 0; i < deltas && !error; ++i) {
+ git_patch *patch = NULL;
+ size_t add = 0, remove = 0, namelen;
+ const git_diff_delta *delta;
+
+ if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
+ break;
+
+ /* keep a count of renames because it will affect formatting */
+ delta = patch->delta;
+
+ /* TODO ugh */
+ namelen = strlen(delta->new_file.path);
+ if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
+ namelen += strlen(delta->old_file.path);
+ stats->renames++;
+ }
+
+ /* and, of course, count the line stats */
+ error = git_patch_line_stats(NULL, &add, &remove, patch);
+
+ git_patch_free(patch);
+
+ stats->filestats[i].insertions = add;
+ stats->filestats[i].deletions = remove;
+
+ total_insertions += add;
+ total_deletions += remove;
+
+ if (stats->max_name < namelen)
+ stats->max_name = namelen;
+ if (stats->max_filestat < add + remove)
+ stats->max_filestat = add + remove;
+ }
+
+ stats->files_changed = deltas;
+ stats->insertions = total_insertions;
+ stats->deletions = total_deletions;
+ stats->max_digits = digits_for_value(stats->max_filestat + 1);
+
+ if (error < 0) {
+ git_diff_stats_free(stats);
+ stats = NULL;
+ }
+
+ *out = stats;
+ return error;
+}
+
+size_t git_diff_stats_files_changed(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->files_changed;
+}
+
+size_t git_diff_stats_insertions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->insertions;
+}
+
+size_t git_diff_stats_deletions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->deletions;
+}
+
+int git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format,
+ size_t width)
+{
+ int error = 0;
+ size_t i;
+ const git_diff_delta *delta;
+
+ assert(out && stats);
+
+ if (format & GIT_DIFF_STATS_NUMBER) {
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__number_to_buf(
+ out, delta, &stats->filestats[i]);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ if (format & GIT_DIFF_STATS_FULL) {
+ if (width > 0) {
+ if (width > stats->max_name + stats->max_digits + 5)
+ width -= (stats->max_name + stats->max_digits + 5);
+ if (width < STATS_FULL_MIN_SCALE)
+ width = STATS_FULL_MIN_SCALE;
+ }
+ if (width > stats->max_filestat)
+ width = 0;
+
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__full_to_buf(
+ out, delta, &stats->filestats[i], stats, width);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
+ git_buf_printf(
+ out, " %" PRIuZ " file%s changed",
+ stats->files_changed, stats->files_changed != 1 ? "s" : "");
+
+ if (stats->insertions || stats->deletions == 0)
+ git_buf_printf(
+ out, ", %" PRIuZ " insertion%s(+)",
+ stats->insertions, stats->insertions != 1 ? "s" : "");
+
+ if (stats->deletions || stats->insertions == 0)
+ git_buf_printf(
+ out, ", %" PRIuZ " deletion%s(-)",
+ stats->deletions, stats->deletions != 1 ? "s" : "");
+
+ git_buf_putc(out, '\n');
+
+ if (git_buf_oom(out))
+ return -1;
+ }
+
+ if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__summary_to_buf(out, delta);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return error;
+}
+
+void git_diff_stats_free(git_diff_stats *stats)
+{
+ if (stats == NULL)
+ return;
+
+ git_diff_free(stats->diff); /* bumped refcount in constructor */
+ git__free(stats->filestats);
+ git__free(stats);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+
+#include "git2/config.h"
+#include "git2/blob.h"
+#include "git2/sys/hashsig.h"
+
+#include "diff.h"
+#include "diff_generate.h"
+#include "path.h"
+#include "fileops.h"
+#include "config.h"
+
+git_diff_delta *git_diff__delta_dup(
+ const git_diff_delta *d, git_pool *pool)
+{
+ git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
+ if (!delta)
+ return NULL;
+
+ memcpy(delta, d, sizeof(git_diff_delta));
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (d->old_file.path != NULL) {
+ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
+ if (delta->old_file.path == NULL)
+ goto fail;
+ }
+
+ if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) {
+ delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
+ if (delta->new_file.path == NULL)
+ goto fail;
+ } else {
+ delta->new_file.path = delta->old_file.path;
+ }
+
+ return delta;
+
+fail:
+ git__free(delta);
+ return NULL;
+}
+
+git_diff_delta *git_diff__merge_like_cgit(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool)
+{
+ git_diff_delta *dup;
+
+ /* Emulate C git for merging two diffs (a la 'git diff <sha>').
+ *
+ * When C git does a diff between the work dir and a tree, it actually
+ * diffs with the index but uses the workdir contents. This emulates
+ * those choices so we can emulate the type of diff.
+ *
+ * We have three file descriptions here, let's call them:
+ * f1 = a->old_file
+ * f2 = a->new_file AND b->old_file
+ * f3 = b->new_file
+ */
+
+ /* If one of the diffs is a conflict, just dup it */
+ if (b->status == GIT_DELTA_CONFLICTED)
+ return git_diff__delta_dup(b, pool);
+ if (a->status == GIT_DELTA_CONFLICTED)
+ return git_diff__delta_dup(a, pool);
+
+ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
+ if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
+ return git_diff__delta_dup(a, pool);
+
+ /* otherwise, base this diff on the 'b' diff */
+ if ((dup = git_diff__delta_dup(b, pool)) == NULL)
+ return NULL;
+
+ /* If 'a' status is uninteresting, then we're done */
+ if (a->status == GIT_DELTA_UNMODIFIED ||
+ a->status == GIT_DELTA_UNTRACKED ||
+ a->status == GIT_DELTA_UNREADABLE)
+ return dup;
+
+ assert(b->status != GIT_DELTA_UNMODIFIED);
+
+ /* A cgit exception is that the diff of a file that is only in the
+ * index (i.e. not in HEAD nor workdir) is given as empty.
+ */
+ if (dup->status == GIT_DELTA_DELETED) {
+ if (a->status == GIT_DELTA_ADDED) {
+ dup->status = GIT_DELTA_UNMODIFIED;
+ dup->nfiles = 2;
+ }
+ /* else don't overwrite DELETE status */
+ } else {
+ dup->status = a->status;
+ dup->nfiles = a->nfiles;
+ }
+
+ git_oid_cpy(&dup->old_file.id, &a->old_file.id);
+ dup->old_file.mode = a->old_file.mode;
+ dup->old_file.size = a->old_file.size;
+ dup->old_file.flags = a->old_file.flags;
+
+ return dup;
+}
+
+int git_diff__merge(
+ git_diff *onto, const git_diff *from, git_diff__merge_cb cb)
+{
+ int error = 0;
+ git_pool onto_pool;
+ git_vector onto_new;
+ git_diff_delta *delta;
+ bool ignore_case, reversed;
+ unsigned int i, j;
+
+ assert(onto && from);
+
+ if (!from->deltas.length)
+ return 0;
+
+ ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0);
+ reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0);
+
+ if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) ||
+ reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) {
+ giterr_set(GITERR_INVALID,
+ "Attempt to merge diffs created with conflicting options");
+ return -1;
+ }
+
+ if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0)
+ return -1;
+
+ git_pool_init(&onto_pool, 1);
+
+ for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
+ git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
+ const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
+ int cmp = !f ? -1 : !o ? 1 :
+ STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
+
+ if (cmp < 0) {
+ delta = git_diff__delta_dup(o, &onto_pool);
+ i++;
+ } else if (cmp > 0) {
+ delta = git_diff__delta_dup(f, &onto_pool);
+ j++;
+ } else {
+ const git_diff_delta *left = reversed ? f : o;
+ const git_diff_delta *right = reversed ? o : f;
+
+ delta = cb(left, right, &onto_pool);
+ i++;
+ j++;
+ }
+
+ /* the ignore rules for the target may not match the source
+ * or the result of a merged delta could be skippable...
+ */
+ if (delta && git_diff_delta__should_skip(&onto->opts, delta)) {
+ git__free(delta);
+ continue;
+ }
+
+ if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
+ break;
+ }
+
+ if (!error) {
+ git_vector_swap(&onto->deltas, &onto_new);
+ git_pool_swap(&onto->pool, &onto_pool);
+
+ if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0)
+ onto->old_src = from->old_src;
+ else
+ onto->new_src = from->new_src;
+
+ /* prefix strings also come from old pool, so recreate those.*/
+ onto->opts.old_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
+ onto->opts.new_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
+ }
+
+ git_vector_free_deep(&onto_new);
+ git_pool_clear(&onto_pool);
+
+ return error;
+}
+
+int git_diff_merge(git_diff *onto, const git_diff *from)
+{
+ return git_diff__merge(onto, from, git_diff__merge_like_cgit);
+}
+
+int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
+
+ GIT_UNUSED(f);
+ return git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
+}
+
+int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
+
+ GIT_UNUSED(f);
+ return git_hashsig_create((git_hashsig **)out, buf, len, opt);
+}
+
+void git_diff_find_similar__hashsig_free(void *sig, void *payload)
+{
+ GIT_UNUSED(payload);
+ git_hashsig_free(sig);
+}
+
+int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload)
+{
+ int error;
+
+ GIT_UNUSED(payload);
+ error = git_hashsig_compare(siga, sigb);
+ if (error < 0)
+ return error;
+
+ *score = error;
+ return 0;
+}
+
+#define DEFAULT_THRESHOLD 50
+#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
+#define DEFAULT_RENAME_LIMIT 200
+
+static int normalize_find_opts(
+ git_diff *diff,
+ git_diff_find_options *opts,
+ const git_diff_find_options *given)
+{
+ git_config *cfg = NULL;
+ git_hashsig_option_t hashsig_opts;
+
+ GITERR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options");
+
+ if (diff->repo != NULL &&
+ git_repository_config__weakptr(&cfg, diff->repo) < 0)
+ return -1;
+
+ if (given)
+ memcpy(opts, given, sizeof(*opts));
+
+ if (!given ||
+ (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG)
+ {
+ if (cfg) {
+ char *rule =
+ git_config__get_string_force(cfg, "diff.renames", "true");
+ int boolval;
+
+ if (!git__parse_bool(&boolval, rule) && !boolval)
+ /* don't set FIND_RENAMES if bool value is false */;
+ else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy"))
+ opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ else
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+
+ git__free(rule);
+ } else {
+ /* set default flag */
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+ }
+ }
+
+ /* some flags imply others */
+
+ if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) {
+ /* if we are only looking for exact matches, then don't turn
+ * MODIFIED items into ADD/DELETE pairs because it's too picky
+ */
+ opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES);
+
+ /* similarly, don't look for self-rewrites to split */
+ opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+ }
+
+ if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+
+ if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
+ opts->flags |= GIT_DIFF_FIND_COPIES;
+
+ if (opts->flags & GIT_DIFF_BREAK_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_REWRITES;
+
+#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
+
+ if (USE_DEFAULT(opts->rename_threshold))
+ opts->rename_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->rename_from_rewrite_threshold))
+ opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->copy_threshold))
+ opts->copy_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->break_rewrite_threshold))
+ opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD;
+
+#undef USE_DEFAULT
+
+ if (!opts->rename_limit) {
+ if (cfg) {
+ opts->rename_limit = git_config__get_int_force(
+ cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT);
+ }
+
+ if (opts->rename_limit <= 0)
+ opts->rename_limit = DEFAULT_RENAME_LIMIT;
+ }
+
+ /* assign the internal metric with whitespace flag as payload */
+ if (!opts->metric) {
+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
+ GITERR_CHECK_ALLOC(opts->metric);
+
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
+
+ if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
+ hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE;
+ else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)
+ hashsig_opts = GIT_HASHSIG_NORMAL;
+ else
+ hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE;
+ hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES;
+ opts->metric->payload = (void *)hashsig_opts;
+ }
+
+ return 0;
+}
+
+static int insert_delete_side_of_split(
+ git_diff *diff, git_vector *onto, const git_diff_delta *delta)
+{
+ /* make new record for DELETED side of split */
+ git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool);
+ GITERR_CHECK_ALLOC(deleted);
+
+ deleted->status = GIT_DELTA_DELETED;
+ deleted->nfiles = 1;
+ memset(&deleted->new_file, 0, sizeof(deleted->new_file));
+ deleted->new_file.path = deleted->old_file.path;
+ deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ return git_vector_insert(onto, deleted);
+}
+
+static int apply_splits_and_deletes(
+ git_diff *diff, size_t expected_size, bool actually_split)
+{
+ git_vector onto = GIT_VECTOR_INIT;
+ size_t i;
+ git_diff_delta *delta;
+
+ if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
+ return -1;
+
+ /* build new delta list without TO_DELETE and splitting TO_SPLIT */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ continue;
+
+ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) {
+ delta->similarity = 0;
+
+ if (insert_delete_side_of_split(diff, &onto, delta) < 0)
+ goto on_error;
+
+ if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR)
+ delta->status = GIT_DELTA_UNTRACKED;
+ else
+ delta->status = GIT_DELTA_ADDED;
+ delta->nfiles = 1;
+ memset(&delta->old_file, 0, sizeof(delta->old_file));
+ delta->old_file.path = delta->new_file.path;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ /* clean up delta before inserting into new list */
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (delta->status != GIT_DELTA_COPIED &&
+ delta->status != GIT_DELTA_RENAMED &&
+ (delta->status != GIT_DELTA_MODIFIED || actually_split))
+ delta->similarity = 0;
+
+ /* insert into new list */
+ if (git_vector_insert(&onto, delta) < 0)
+ goto on_error;
+ }
+
+ /* cannot return an error past this point */
+
+ /* free deltas from old list that didn't make it to the new one */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ git__free(delta);
+ }
+
+ /* swap new delta list into place */
+ git_vector_swap(&diff->deltas, &onto);
+ git_vector_free(&onto);
+ git_vector_sort(&diff->deltas);
+
+ return 0;
+
+on_error:
+ git_vector_free_deep(&onto);
+
+ return -1;
+}
+
+GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx)
+{
+ git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2);
+ return (idx & 1) ? &delta->new_file : &delta->old_file;
+}
+
+typedef struct {
+ size_t idx;
+ git_iterator_type_t src;
+ git_repository *repo;
+ git_diff_file *file;
+ git_buf data;
+ git_odb_object *odb_obj;
+ git_blob *blob;
+} similarity_info;
+
+static int similarity_init(
+ similarity_info *info, git_diff *diff, size_t file_idx)
+{
+ info->idx = file_idx;
+ info->src = (file_idx & 1) ? diff->new_src : diff->old_src;
+ info->repo = diff->repo;
+ info->file = similarity_get_file(diff, file_idx);
+ info->odb_obj = NULL;
+ info->blob = NULL;
+ git_buf_init(&info->data, 0);
+
+ if (info->file->size > 0 || info->src == GIT_ITERATOR_TYPE_WORKDIR)
+ return 0;
+
+ return git_diff_file__resolve_zero_size(
+ info->file, &info->odb_obj, info->repo);
+}
+
+static int similarity_sig(
+ similarity_info *info,
+ const git_diff_find_options *opts,
+ void **cache)
+{
+ int error = 0;
+ git_diff_file *file = info->file;
+
+ if (info->src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_buf_joinpath(
+ &info->data, git_repository_workdir(info->repo), file->path)) < 0)
+ return error;
+
+ /* if path is not a regular file, just skip this item */
+ if (!git_path_isfile(info->data.ptr))
+ return 0;
+
+ /* TODO: apply wd-to-odb filters to file data if necessary */
+
+ error = opts->metric->file_signature(
+ &cache[info->idx], info->file,
+ info->data.ptr, opts->metric->payload);
+ } else {
+ /* if we didn't initially know the size, we might have an odb_obj
+ * around from earlier, so convert that, otherwise load the blob now
+ */
+ if (info->odb_obj != NULL)
+ error = git_object__from_odb_object(
+ (git_object **)&info->blob, info->repo,
+ info->odb_obj, GIT_OBJ_BLOB);
+ else
+ error = git_blob_lookup(&info->blob, info->repo, &file->id);
+
+ if (error < 0) {
+ /* if lookup fails, just skip this item in similarity calc */
+ giterr_clear();
+ } else {
+ size_t sz;
+
+ /* index size may not be actual blob size if filtered */
+ if (file->size != git_blob_rawsize(info->blob))
+ file->size = git_blob_rawsize(info->blob);
+
+ sz = (size_t)(git__is_sizet(file->size) ? file->size : -1);
+
+ error = opts->metric->buffer_signature(
+ &cache[info->idx], info->file,
+ git_blob_rawcontent(info->blob), sz, opts->metric->payload);
+ }
+ }
+
+ return error;
+}
+
+static void similarity_unload(similarity_info *info)
+{
+ if (info->odb_obj)
+ git_odb_object_free(info->odb_obj);
+
+ if (info->blob)
+ git_blob_free(info->blob);
+ else
+ git_buf_free(&info->data);
+}
+
+#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
+
+/* - score < 0 means files cannot be compared
+ * - score >= 100 means files are exact match
+ * - score == 0 means files are completely different
+ */
+static int similarity_measure(
+ int *score,
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ void **cache,
+ size_t a_idx,
+ size_t b_idx)
+{
+ git_diff_file *a_file = similarity_get_file(diff, a_idx);
+ git_diff_file *b_file = similarity_get_file(diff, b_idx);
+ bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
+ int error = 0;
+ similarity_info a_info, b_info;
+
+ *score = -1;
+
+ /* don't try to compare files of different types */
+ if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
+ return 0;
+
+ /* if exact match is requested, force calculation of missing OIDs now */
+ if (exact_match) {
+ if (git_oid_iszero(&a_file->id) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(&a_file->id,
+ diff, a_file->path, a_file->mode, a_file->size))
+ a_file->flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (git_oid_iszero(&b_file->id) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(&b_file->id,
+ diff, b_file->path, b_file->mode, b_file->size))
+ b_file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ /* check OID match as a quick test */
+ if (git_oid__cmp(&a_file->id, &b_file->id) == 0) {
+ *score = 100;
+ return 0;
+ }
+
+ /* don't calculate signatures if we are doing exact match */
+ if (exact_match) {
+ *score = 0;
+ return 0;
+ }
+
+ memset(&a_info, 0, sizeof(a_info));
+ memset(&b_info, 0, sizeof(b_info));
+
+ /* set up similarity data (will try to update missing file sizes) */
+ if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0)
+ goto cleanup;
+
+ /* check if file sizes are nowhere near each other */
+ if (a_file->size > 127 &&
+ b_file->size > 127 &&
+ (a_file->size > (b_file->size << 3) ||
+ b_file->size > (a_file->size << 3)))
+ goto cleanup;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx]) {
+ if ((error = similarity_sig(&a_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+ if (!cache[b_idx]) {
+ if ((error = similarity_sig(&b_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+
+ /* calculate similarity provided that the metric choose to process
+ * both the a and b files (some may not if file is too big, etc).
+ */
+ if (cache[a_idx] && cache[b_idx])
+ error = opts->metric->similarity(
+ score, cache[a_idx], cache[b_idx], opts->metric->payload);
+
+cleanup:
+ similarity_unload(&a_info);
+ similarity_unload(&b_info);
+
+ return error;
+}
+
+static int calc_self_similarity(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ int error, similarity = -1;
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0)
+ return 0;
+
+ error = similarity_measure(
+ &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1);
+ if (error < 0)
+ return error;
+
+ if (similarity >= 0) {
+ delta->similarity = (uint16_t)similarity;
+ delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY;
+ }
+
+ return 0;
+}
+
+static bool is_rename_target(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't plain blobs */
+ if (!GIT_MODE_ISBLOB(delta->new_file.mode))
+ return false;
+
+ /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as
+ * targets; maybe include UNTRACKED if requested.
+ */
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_CONFLICTED:
+ return false;
+
+ case GIT_DELTA_MODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+
+ case GIT_DELTA_UNTRACKED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
+ return false;
+ break;
+
+ default: /* all other status values should be checked */
+ break;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET;
+ return true;
+}
+
+static bool is_rename_source(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't blobs */
+ if (!GIT_MODE_ISBLOB(delta->old_file.mode))
+ return false;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_UNREADABLE:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_CONFLICTED:
+ return false;
+
+ case GIT_DELTA_DELETED:
+ case GIT_DELTA_TYPECHANGE:
+ break;
+
+ case GIT_DELTA_UNMODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ return false;
+ if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED))
+ delta->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ break;
+
+ default: /* MODIFIED, RENAMED, COPIED */
+ /* if we're finding copies, this could be a source */
+ if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ break;
+
+ /* otherwise, this is only a source if we can split it */
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE;
+ return true;
+}
+
+GIT_INLINE(bool) delta_is_split(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_TYPECHANGE ||
+ (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0);
+}
+
+GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_UNTRACKED ||
+ delta->status == GIT_DELTA_UNREADABLE ||
+ delta->status == GIT_DELTA_IGNORED);
+}
+
+GIT_INLINE(void) delta_make_rename(
+ git_diff_delta *to, const git_diff_delta *from, uint16_t similarity)
+{
+ to->status = GIT_DELTA_RENAMED;
+ to->similarity = similarity;
+ to->nfiles = 2;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+}
+
+typedef struct {
+ size_t idx;
+ uint16_t similarity;
+} diff_find_match;
+
+int git_diff_find_similar(
+ git_diff *diff,
+ const git_diff_find_options *given_opts)
+{
+ size_t s, t;
+ int error = 0, result;
+ uint16_t similarity;
+ git_diff_delta *src, *tgt;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ size_t num_deltas, num_srcs = 0, num_tgts = 0;
+ size_t tried_srcs = 0, tried_tgts = 0;
+ size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
+ size_t sigcache_size;
+ void **sigcache = NULL; /* cache of similarity metric file signatures */
+ diff_find_match *tgt2src = NULL;
+ diff_find_match *src2tgt = NULL;
+ diff_find_match *tgt2src_copy = NULL;
+ diff_find_match *best_match;
+ git_diff_file swap;
+
+ if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
+ return error;
+
+ num_deltas = diff->deltas.length;
+
+ /* TODO: maybe abort if deltas.length > rename_limit ??? */
+ if (!git__is_uint32(num_deltas))
+ goto cleanup;
+
+ /* No flags set; nothing to do */
+ if ((opts.flags & GIT_DIFF_FIND_ALL) == 0)
+ goto cleanup;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2);
+ sigcache = git__calloc(sigcache_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(sigcache);
+
+ /* Label rename sources and targets
+ *
+ * This will also set self-similarity scores for MODIFIED files and
+ * mark them for splitting if break-rewrites is enabled
+ */
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ if (is_rename_source(diff, &opts, t, sigcache))
+ ++num_srcs;
+
+ if (is_rename_target(diff, &opts, t, sigcache))
+ ++num_tgts;
+
+ if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
+ num_rewrites++;
+ }
+
+ /* if there are no candidate srcs or tgts, we're done */
+ if (!num_srcs || !num_tgts)
+ goto cleanup;
+
+ src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(src2tgt);
+ tgt2src = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src);
+
+ if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
+ tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src_copy);
+ }
+
+ /*
+ * Find best-fit matches for rename / copy candidates
+ */
+
+find_best_matches:
+ tried_tgts = num_bumped = 0;
+
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
+ continue;
+
+ tried_srcs = 0;
+
+ git_vector_foreach(&diff->deltas, s, src) {
+ /* skip things that are not rename sources */
+ if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0)
+ continue;
+
+ /* calculate similarity for this pair and find best match */
+ if (s == t)
+ result = -1; /* don't measure self-similarity here */
+ else if ((error = similarity_measure(
+ &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0)
+ goto cleanup;
+
+ if (result < 0)
+ continue;
+ similarity = (uint16_t)result;
+
+ /* is this a better rename? */
+ if (tgt2src[t].similarity < similarity &&
+ src2tgt[s].similarity < similarity)
+ {
+ /* eject old mapping */
+ if (src2tgt[s].similarity > 0) {
+ tgt2src[src2tgt[s].idx].similarity = 0;
+ num_bumped++;
+ }
+ if (tgt2src[t].similarity > 0) {
+ src2tgt[tgt2src[t].idx].similarity = 0;
+ num_bumped++;
+ }
+
+ /* write new mapping */
+ tgt2src[t].idx = s;
+ tgt2src[t].similarity = similarity;
+ src2tgt[s].idx = t;
+ src2tgt[s].similarity = similarity;
+ }
+
+ /* keep best absolute match for copies */
+ if (tgt2src_copy != NULL &&
+ tgt2src_copy[t].similarity < similarity)
+ {
+ tgt2src_copy[t].idx = s;
+ tgt2src_copy[t].similarity = similarity;
+ }
+
+ if (++tried_srcs >= num_srcs)
+ break;
+
+ /* cap on maximum targets we'll examine (per "tgt" file) */
+ if (tried_srcs > opts.rename_limit)
+ break;
+ }
+
+ if (++tried_tgts >= num_tgts)
+ break;
+ }
+
+ if (num_bumped > 0) /* try again if we bumped some items */
+ goto find_best_matches;
+
+ /*
+ * Rewrite the diffs with renames / copies
+ */
+
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
+ continue;
+
+ /* check if this delta was the target of a similarity */
+ if (tgt2src[t].similarity)
+ best_match = &tgt2src[t];
+ else if (tgt2src_copy && tgt2src_copy[t].similarity)
+ best_match = &tgt2src_copy[t];
+ else
+ continue;
+
+ s = best_match->idx;
+ src = GIT_VECTOR_GET(&diff->deltas, s);
+
+ /* possible scenarios:
+ * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
+ * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE
+ * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME
+ * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT
+ * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
+ */
+
+ if (src->status == GIT_DELTA_DELETED) {
+
+ if (delta_is_new_only(tgt)) {
+
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
+
+ delta_make_rename(tgt, src, best_match->similarity);
+
+ src->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ num_rewrites++;
+ } else {
+ assert(delta_is_split(tgt));
+
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
+
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
+
+ assert(src->status == GIT_DELTA_DELETED);
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+ memset(&src->new_file, 0, sizeof(src->new_file));
+ src->new_file.path = src->old_file.path;
+ src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ num_updates++;
+
+ if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = s;
+ }
+ }
+ }
+
+ else if (delta_is_split(src)) {
+
+ if (delta_is_new_only(tgt)) {
+
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
+
+ delta_make_rename(tgt, src, best_match->similarity);
+
+ src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
+ GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
+ src->nfiles = 1;
+ memset(&src->old_file, 0, sizeof(src->old_file));
+ src->old_file.path = src->new_file.path;
+ src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+
+ num_updates++;
+ } else {
+ assert(delta_is_split(src));
+
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
+
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
+ num_updates++;
+
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+
+ /* if we've just swapped the new element into the correct
+ * place, clear the SPLIT flag
+ */
+ if (tgt2src[s].idx == t &&
+ tgt2src[s].similarity >
+ opts.rename_from_rewrite_threshold) {
+ src->status = GIT_DELTA_RENAMED;
+ src->similarity = tgt2src[s].similarity;
+ tgt2src[s].similarity = 0;
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+ /* otherwise, if we just overwrote a source, update mapping */
+ else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = s;
+ }
+
+ num_updates++;
+ }
+ }
+
+ else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
+ if (tgt2src_copy[t].similarity < opts.copy_threshold)
+ continue;
+
+ /* always use best possible source for copy */
+ best_match = &tgt2src_copy[t];
+ src = GIT_VECTOR_GET(&diff->deltas, best_match->idx);
+
+ if (delta_is_split(tgt)) {
+ error = insert_delete_side_of_split(diff, &diff->deltas, tgt);
+ if (error < 0)
+ goto cleanup;
+ num_rewrites--;
+ }
+
+ if (!delta_is_split(tgt) && !delta_is_new_only(tgt))
+ continue;
+
+ tgt->status = GIT_DELTA_COPIED;
+ tgt->similarity = best_match->similarity;
+ tgt->nfiles = 2;
+ memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file));
+ tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+
+ num_updates++;
+ }
+ }
+
+ /*
+ * Actually split and delete entries as needed
+ */
+
+ if (num_rewrites > 0 || num_updates > 0)
+ error = apply_splits_and_deletes(
+ diff, diff->deltas.length - num_rewrites,
+ FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) &&
+ !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY));
+
+cleanup:
+ git__free(tgt2src);
+ git__free(src2tgt);
+ git__free(tgt2src_copy);
+
+ if (sigcache) {
+ for (t = 0; t < num_deltas * 2; ++t) {
+ if (sigcache[t] != NULL)
+ opts.metric->free_signature(sigcache[t], opts.metric->payload);
+ }
+ git__free(sigcache);
+ }
+
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ return error;
+}
+
+#undef FLAG_SET
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_tform_h__
+#define INCLUDE_diff_tform_h__
+
+extern int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p);
+
+extern int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
+
+extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
+
+extern int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload);
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/errors.h"
+#include "common.h"
+#include "diff.h"
+#include "diff_driver.h"
+#include "diff_xdiff.h"
+#include "patch_generate.h"
+
+static int git_xdiff_scan_int(const char **str, int *value)
+{
+ const char *scan = *str;
+ int v = 0, digits = 0;
+ /* find next digit */
+ for (scan = *str; *scan && !git__isdigit(*scan); scan++);
+ /* parse next number */
+ for (; git__isdigit(*scan); scan++, digits++)
+ v = (v * 10) + (*scan - '0');
+ *str = scan;
+ *value = v;
+ return (digits > 0) ? 0 : -1;
+}
+
+static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header)
+{
+ /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
+ if (*header != '@')
+ goto fail;
+ if (git_xdiff_scan_int(&header, &hunk->old_start) < 0)
+ goto fail;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0)
+ goto fail;
+ } else
+ hunk->old_lines = 1;
+ if (git_xdiff_scan_int(&header, &hunk->new_start) < 0)
+ goto fail;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0)
+ goto fail;
+ } else
+ hunk->new_lines = 1;
+ if (hunk->old_start < 0 || hunk->new_start < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_INVALID, "Malformed hunk header from xdiff");
+ return -1;
+}
+
+typedef struct {
+ git_xdiff_output *xo;
+ git_patch_generated *patch;
+ git_diff_hunk hunk;
+ int old_lineno, new_lineno;
+ mmfile_t xd_old_data, xd_new_data;
+} git_xdiff_info;
+
+static int diff_update_lines(
+ git_xdiff_info *info,
+ git_diff_line *line,
+ const char *content,
+ size_t content_len)
+{
+ const char *scan = content, *scan_end = content + content_len;
+
+ for (line->num_lines = 0; scan < scan_end; ++scan)
+ if (*scan == '\n')
+ ++line->num_lines;
+
+ line->content = content;
+ line->content_len = content_len;
+
+ /* expect " "/"-"/"+", then data */
+ switch (line->origin) {
+ case GIT_DIFF_LINE_ADDITION:
+ case GIT_DIFF_LINE_DEL_EOFNL:
+ line->old_lineno = -1;
+ line->new_lineno = info->new_lineno;
+ info->new_lineno += (int)line->num_lines;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ case GIT_DIFF_LINE_ADD_EOFNL:
+ line->old_lineno = info->old_lineno;
+ line->new_lineno = -1;
+ info->old_lineno += (int)line->num_lines;
+ break;
+ case GIT_DIFF_LINE_CONTEXT:
+ case GIT_DIFF_LINE_CONTEXT_EOFNL:
+ line->old_lineno = info->old_lineno;
+ line->new_lineno = info->new_lineno;
+ info->old_lineno += (int)line->num_lines;
+ info->new_lineno += (int)line->num_lines;
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown diff line origin %02x",
+ (unsigned int)line->origin);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
+{
+ git_xdiff_info *info = priv;
+ git_patch_generated *patch = info->patch;
+ const git_diff_delta *delta = patch->base.delta;
+ git_patch_generated_output *output = &info->xo->output;
+ git_diff_line line;
+
+ if (len == 1) {
+ output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr);
+ if (output->error < 0)
+ return output->error;
+
+ info->hunk.header_len = bufs[0].size;
+ if (info->hunk.header_len >= sizeof(info->hunk.header))
+ info->hunk.header_len = sizeof(info->hunk.header) - 1;
+ memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len);
+ info->hunk.header[info->hunk.header_len] = '\0';
+
+ if (output->hunk_cb != NULL &&
+ (output->error = output->hunk_cb(
+ delta, &info->hunk, output->payload)))
+ return output->error;
+
+ info->old_lineno = info->hunk.old_start;
+ info->new_lineno = info->hunk.new_start;
+ }
+
+ if (len == 2 || len == 3) {
+ /* expect " "/"-"/"+", then data */
+ line.origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
+ GIT_DIFF_LINE_CONTEXT;
+
+ if (line.origin == GIT_DIFF_LINE_ADDITION)
+ line.content_offset = bufs[1].ptr - info->xd_new_data.ptr;
+ else if (line.origin == GIT_DIFF_LINE_DELETION)
+ line.content_offset = bufs[1].ptr - info->xd_old_data.ptr;
+ else
+ line.content_offset = -1;
+
+ output->error = diff_update_lines(
+ info, &line, bufs[1].ptr, bufs[1].size);
+
+ if (!output->error && output->data_cb != NULL)
+ output->error = output->data_cb(
+ delta, &info->hunk, &line, output->payload);
+ }
+
+ if (len == 3 && !output->error) {
+ /* If we have a '+' and a third buf, then we have added a line
+ * without a newline and the old code had one, so DEL_EOFNL.
+ * If we have a '-' and a third buf, then we have removed a line
+ * with out a newline but added a blank line, so ADD_EOFNL.
+ */
+ line.origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
+ GIT_DIFF_LINE_CONTEXT_EOFNL;
+
+ line.content_offset = -1;
+
+ output->error = diff_update_lines(
+ info, &line, bufs[2].ptr, bufs[2].size);
+
+ if (!output->error && output->data_cb != NULL)
+ output->error = output->data_cb(
+ delta, &info->hunk, &line, output->payload);
+ }
+
+ return output->error;
+}
+
+static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch)
+{
+ git_xdiff_output *xo = (git_xdiff_output *)output;
+ git_xdiff_info info;
+ git_diff_find_context_payload findctxt;
+
+ memset(&info, 0, sizeof(info));
+ info.patch = patch;
+ info.xo = xo;
+
+ xo->callback.priv = &info;
+
+ git_diff_find_context_init(
+ &xo->config.find_func, &findctxt, git_patch_generated_driver(patch));
+ xo->config.find_func_priv = &findctxt;
+
+ if (xo->config.find_func != NULL)
+ xo->config.flags |= XDL_EMIT_FUNCNAMES;
+ else
+ xo->config.flags &= ~XDL_EMIT_FUNCNAMES;
+
+ /* TODO: check ofile.opts_flags to see if driver-specific per-file
+ * updates are needed to xo->params.flags
+ */
+
+ git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
+ git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
+
+ if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
+ info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
+ giterr_set(GITERR_INVALID, "files too large for diff");
+ return -1;
+ }
+
+ xdl_diff(&info.xd_old_data, &info.xd_new_data,
+ &xo->params, &xo->config, &xo->callback);
+
+ git_diff_find_context_clear(&findctxt);
+
+ return xo->output.error;
+}
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ xo->output.diff_cb = git_xdiff;
+
+ xo->config.ctxlen = opts ? opts->context_lines : 3;
+ xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0;
+
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE)
+ xo->params.flags |= XDF_WHITESPACE_FLAGS;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+ if (flags & GIT_DIFF_PATIENCE)
+ xo->params.flags |= XDF_PATIENCE_DIFF;
+ if (flags & GIT_DIFF_MINIMAL)
+ xo->params.flags |= XDF_NEED_MINIMAL;
+
+ xo->callback.outf = git_xdiff_cb;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_xdiff_h__
+#define INCLUDE_diff_xdiff_h__
+
+#include "diff.h"
+#include "xdiff/xdiff.h"
+#include "patch_generate.h"
+
+/* xdiff cannot cope with large files. these files should not be passed to
+ * xdiff. callers should treat these large files as binary.
+ */
+#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023)
+
+/* A git_xdiff_output is a git_patch_generate_output with extra fields
+ * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb
+ * field of the output to use xdiff to generate the diffs.
+ */
+typedef struct {
+ git_patch_generated_output output;
+
+ xdemitconf_t config;
+ xpparam_t params;
+ xdemitcb_t callback;
+} git_xdiff_output;
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "global.h"
+#include "posix.h"
+#include "buffer.h"
+
+/********************************************
+ * New error handling
+ ********************************************/
+
+static git_error g_git_oom_error = {
+ "Out of memory",
+ GITERR_NOMEMORY
+};
+
+static void set_error_from_buffer(int error_class)
+{
+ git_error *error = &GIT_GLOBAL->error_t;
+ git_buf *buf = &GIT_GLOBAL->error_buf;
+
+ error->message = buf->ptr;
+ error->klass = error_class;
+
+ GIT_GLOBAL->last_error = error;
+}
+
+static void set_error(int error_class, char *string)
+{
+ git_buf *buf = &GIT_GLOBAL->error_buf;
+
+ git_buf_clear(buf);
+ if (string) {
+ git_buf_puts(buf, string);
+ git__free(string);
+ }
+
+ set_error_from_buffer(error_class);
+}
+
+void giterr_set_oom(void)
+{
+ GIT_GLOBAL->last_error = &g_git_oom_error;
+}
+
+void giterr_set(int error_class, const char *string, ...)
+{
+ va_list arglist;
+#ifdef GIT_WIN32
+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0;
+#endif
+ int error_code = (error_class == GITERR_OS) ? errno : 0;
+ git_buf *buf = &GIT_GLOBAL->error_buf;
+
+ git_buf_clear(buf);
+ if (string) {
+ va_start(arglist, string);
+ git_buf_vprintf(buf, string, arglist);
+ va_end(arglist);
+
+ if (error_class == GITERR_OS)
+ git_buf_PUTS(buf, ": ");
+ }
+
+ if (error_class == GITERR_OS) {
+#ifdef GIT_WIN32
+ char * win32_error = git_win32_get_error_message(win32_error_code);
+ if (win32_error) {
+ git_buf_puts(buf, win32_error);
+ git__free(win32_error);
+
+ SetLastError(0);
+ }
+ else
+#endif
+ if (error_code)
+ git_buf_puts(buf, strerror(error_code));
+
+ if (error_code)
+ errno = 0;
+ }
+
+ if (!git_buf_oom(buf))
+ set_error_from_buffer(error_class);
+}
+
+void giterr_set_str(int error_class, const char *string)
+{
+ git_buf *buf = &GIT_GLOBAL->error_buf;
+
+ assert(string);
+
+ if (!string)
+ return;
+
+ git_buf_clear(buf);
+ git_buf_puts(buf, string);
+ if (!git_buf_oom(buf))
+ set_error_from_buffer(error_class);
+}
+
+int giterr_set_regex(const regex_t *regex, int error_code)
+{
+ char error_buf[1024];
+
+ assert(error_code);
+
+ regerror(error_code, regex, error_buf, sizeof(error_buf));
+ giterr_set_str(GITERR_REGEX, error_buf);
+
+ if (error_code == REG_NOMATCH)
+ return GIT_ENOTFOUND;
+
+ return GIT_EINVALIDSPEC;
+}
+
+void giterr_clear(void)
+{
+ if (GIT_GLOBAL->last_error != NULL) {
+ set_error(0, NULL);
+ GIT_GLOBAL->last_error = NULL;
+ }
+
+ errno = 0;
+#ifdef GIT_WIN32
+ SetLastError(0);
+#endif
+}
+
+const git_error *giterr_last(void)
+{
+ return GIT_GLOBAL->last_error;
+}
+
+int giterr_state_capture(git_error_state *state, int error_code)
+{
+ git_error *error = GIT_GLOBAL->last_error;
+ git_buf *error_buf = &GIT_GLOBAL->error_buf;
+
+ memset(state, 0, sizeof(git_error_state));
+
+ if (!error_code)
+ return 0;
+
+ state->error_code = error_code;
+ state->oom = (error == &g_git_oom_error);
+
+ if (error) {
+ state->error_msg.klass = error->klass;
+
+ if (state->oom)
+ state->error_msg.message = g_git_oom_error.message;
+ else
+ state->error_msg.message = git_buf_detach(error_buf);
+ }
+
+ giterr_clear();
+ return error_code;
+}
+
+int giterr_state_restore(git_error_state *state)
+{
+ int ret = 0;
+
+ giterr_clear();
+
+ if (state && state->error_msg.message) {
+ if (state->oom)
+ giterr_set_oom();
+ else
+ set_error(state->error_msg.klass, state->error_msg.message);
+
+ ret = state->error_code;
+ memset(state, 0, sizeof(git_error_state));
+ }
+
+ return ret;
+}
+
+void giterr_state_free(git_error_state *state)
+{
+ if (!state)
+ return;
+
+ if (!state->oom)
+ git__free(state->error_msg.message);
+
+ memset(state, 0, sizeof(git_error_state));
+}
+
+int giterr_system_last(void)
+{
+#ifdef GIT_WIN32
+ return GetLastError();
+#else
+ return errno;
+#endif
+}
+
+void giterr_system_set(int code)
+{
+#ifdef GIT_WIN32
+ SetLastError(code);
+#else
+ errno = code;
+#endif
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/oid.h"
+#include "git2/refs.h"
+#include "git2/revwalk.h"
+#include "git2/transport.h"
+
+#include "common.h"
+#include "remote.h"
+#include "refspec.h"
+#include "pack.h"
+#include "fetch.h"
+#include "netops.h"
+#include "repository.h"
+#include "refs.h"
+
+static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, git_refspec *tagspec, git_remote_autotag_option_t tagopt)
+{
+ int match = 0;
+
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ /*
+ * If tagopt is --tags, always request tags
+ * in addition to the remote's refspecs
+ */
+ if (git_refspec_src_matches(tagspec, head->name))
+ match = 1;
+ }
+
+ if (!match && git_remote__matching_refspec(remote, head->name))
+ match = 1;
+
+ if (!match)
+ return 0;
+
+ /* If we have the object, mark it so we don't ask for it */
+ if (git_odb_exists(odb, &head->oid)) {
+ head->local = 1;
+ }
+ else
+ remote->need_pack = 1;
+
+ return git_vector_insert(&remote->refs, head);
+}
+
+static int filter_wants(git_remote *remote, const git_fetch_options *opts)
+{
+ git_remote_head **heads;
+ git_refspec tagspec, head;
+ int error = 0;
+ git_odb *odb;
+ size_t i, heads_len;
+ git_remote_autotag_option_t tagopt = remote->download_tags;
+
+ if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED)
+ tagopt = opts->download_tags;
+
+ git_vector_clear(&remote->refs);
+ if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0)
+ return error;
+
+ /*
+ * The fetch refspec can be NULL, and what this means is that the
+ * user didn't specify one. This is fine, as it means that we're
+ * not interested in any particular branch but just the remote's
+ * HEAD, which will be stored in FETCH_HEAD after the fetch.
+ */
+ if (remote->active_refspecs.length == 0) {
+ if ((error = git_refspec__parse(&head, "HEAD", true)) < 0)
+ goto cleanup;
+
+ error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs);
+ git_refspec__free(&head);
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ goto cleanup;
+
+ if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if ((error = maybe_want(remote, heads[i], odb, &tagspec, tagopt)) < 0)
+ break;
+ }
+
+cleanup:
+ git_refspec__free(&tagspec);
+
+ return error;
+}
+
+/*
+ * In this first version, we push all our refs in and start sending
+ * them out. When we get an ACK we hide that commit and continue
+ * traversing until we're done
+ */
+int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts)
+{
+ git_transport *t = remote->transport;
+
+ remote->need_pack = 0;
+
+ if (filter_wants(remote, opts) < 0) {
+ giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
+ return -1;
+ }
+
+ /* Don't try to negotiate when we don't want anything */
+ if (!remote->need_pack)
+ return 0;
+
+ /*
+ * Now we have everything set up so we can start tell the
+ * server what we want and what we have.
+ */
+ return t->negotiate_fetch(t,
+ remote->repo,
+ (const git_remote_head * const *)remote->refs.contents,
+ remote->refs.length);
+}
+
+int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks)
+{
+ git_transport *t = remote->transport;
+ git_transfer_progress_cb progress = NULL;
+ void *payload = NULL;
+
+ if (!remote->need_pack)
+ return 0;
+
+ if (callbacks) {
+ progress = callbacks->transfer_progress;
+ payload = callbacks->payload;
+ }
+
+ return t->download_pack(t, remote->repo, &remote->stats, progress, payload);
+}
+
+int git_fetch_init_options(git_fetch_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_fetch_options, GIT_FETCH_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_fetch_h__
+#define INCLUDE_fetch_h__
+
+#include "netops.h"
+
+int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts);
+
+int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks);
+
+int git_fetch_setup_walk(git_revwalk **out, git_repository *repo);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/types.h"
+#include "git2/oid.h"
+
+#include "fetchhead.h"
+#include "common.h"
+#include "buffer.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "refs.h"
+#include "repository.h"
+
+int git_fetchhead_ref_cmp(const void *a, const void *b)
+{
+ const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
+ const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
+
+ if (one->is_merge && !two->is_merge)
+ return -1;
+ if (two->is_merge && !one->is_merge)
+ return 1;
+
+ if (one->ref_name && two->ref_name)
+ return strcmp(one->ref_name, two->ref_name);
+ else if (one->ref_name)
+ return -1;
+ else if (two->ref_name)
+ return 1;
+
+ return 0;
+}
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url)
+{
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
+ GITERR_CHECK_ALLOC(fetchhead_ref);
+
+ memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
+
+ git_oid_cpy(&fetchhead_ref->oid, oid);
+ fetchhead_ref->is_merge = is_merge;
+
+ if (ref_name)
+ fetchhead_ref->ref_name = git__strdup(ref_name);
+
+ if (remote_url)
+ fetchhead_ref->remote_url = git__strdup(remote_url);
+
+ *out = fetchhead_ref;
+
+ return 0;
+}
+
+static int fetchhead_ref_write(
+ git_filebuf *file,
+ git_fetchhead_ref *fetchhead_ref)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ const char *type, *name;
+ int head = 0;
+
+ assert(file && fetchhead_ref);
+
+ git_oid_fmt(oid, &fetchhead_ref->oid);
+ oid[GIT_OID_HEXSZ] = '\0';
+
+ if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
+ type = "branch ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
+ } else if(git__prefixcmp(fetchhead_ref->ref_name,
+ GIT_REFS_TAGS_DIR) == 0) {
+ type = "tag ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
+ } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) {
+ head = 1;
+ } else {
+ type = "";
+ name = fetchhead_ref->ref_name;
+ }
+
+ if (head)
+ return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url);
+
+ return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
+ oid,
+ (fetchhead_ref->is_merge) ? "" : "not-for-merge",
+ type,
+ name,
+ fetchhead_ref->remote_url);
+}
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf path = GIT_BUF_INIT;
+ unsigned int i;
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(repo && fetchhead_refs);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) {
+ git_buf_free(&path);
+ return -1;
+ }
+
+ git_buf_free(&path);
+
+ git_vector_sort(fetchhead_refs);
+
+ git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
+ fetchhead_ref_write(&file, fetchhead_ref);
+
+ return git_filebuf_commit(&file);
+}
+
+static int fetchhead_ref_parse(
+ git_oid *oid,
+ unsigned int *is_merge,
+ git_buf *ref_name,
+ const char **remote_url,
+ char *line,
+ size_t line_num)
+{
+ char *oid_str, *is_merge_str, *desc, *name = NULL;
+ const char *type = NULL;
+ int error = 0;
+
+ *remote_url = NULL;
+
+ if (!*line) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Empty line in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
+ if ((oid_str = git__strsep(&line, "\t")) == NULL) {
+ oid_str = line;
+ line += strlen(line);
+
+ *is_merge = 1;
+ }
+
+ if (strlen(oid_str) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid object ID in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ if (git_oid_fromstr(oid, oid_str) < 0) {
+ const git_error *oid_err = giterr_last();
+ const char *err_msg = oid_err ? oid_err->message : "Invalid object ID";
+
+ giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ,
+ err_msg, line_num);
+ return -1;
+ }
+
+ /* Parse new data from newer git clients */
+ if (*line) {
+ if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description data in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ if (*is_merge_str == '\0')
+ *is_merge = 1;
+ else if (strcmp(is_merge_str, "not-for-merge") == 0)
+ *is_merge = 0;
+ else {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ if ((desc = line) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ if (git__prefixcmp(desc, "branch '") == 0) {
+ type = GIT_REFS_HEADS_DIR;
+ name = desc + 8;
+ } else if (git__prefixcmp(desc, "tag '") == 0) {
+ type = GIT_REFS_TAGS_DIR;
+ name = desc + 5;
+ } else if (git__prefixcmp(desc, "'") == 0)
+ name = desc + 1;
+
+ if (name) {
+ if ((desc = strstr(name, "' ")) == NULL ||
+ git__prefixcmp(desc, "' of ") != 0) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %"PRIuZ, line_num);
+ return -1;
+ }
+
+ *desc = '\0';
+ desc += 5;
+ }
+
+ *remote_url = desc;
+ }
+
+ git_buf_clear(ref_name);
+
+ if (type)
+ git_buf_join(ref_name, '/', type, name);
+ else if(name)
+ git_buf_puts(ref_name, name);
+
+ return error;
+}
+
+int git_repository_fetchhead_foreach(git_repository *repo,
+ git_repository_fetchhead_foreach_cb cb,
+ void *payload)
+{
+ git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;
+ const char *ref_name;
+ git_oid oid;
+ const char *remote_url;
+ unsigned int is_merge = 0;
+ char *buffer, *line;
+ size_t line_num = 0;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
+ goto done;
+
+ buffer = file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ ++line_num;
+
+ if ((error = fetchhead_ref_parse(
+ &oid, &is_merge, &name, &remote_url, line, line_num)) < 0)
+ goto done;
+
+ if (git_buf_len(&name) > 0)
+ ref_name = git_buf_cstr(&name);
+ else
+ ref_name = NULL;
+
+ error = cb(ref_name, remote_url, &oid, is_merge, payload);
+ if (error) {
+ giterr_set_after_callback(error);
+ goto done;
+ }
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_FETCHHEAD, "No EOL at line %"PRIuZ, line_num+1);
+ error = -1;
+ goto done;
+ }
+
+done:
+ git_buf_free(&file);
+ git_buf_free(&path);
+ git_buf_free(&name);
+
+ return error;
+}
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
+{
+ if (fetchhead_ref == NULL)
+ return;
+
+ git__free(fetchhead_ref->remote_url);
+ git__free(fetchhead_ref->ref_name);
+ git__free(fetchhead_ref);
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_fetchhead_h__
+#define INCLUDE_fetchhead_h__
+
+#include "vector.h"
+
+typedef struct git_fetchhead_ref {
+ git_oid oid;
+ unsigned int is_merge;
+ char *ref_name;
+ char *remote_url;
+} git_fetchhead_ref;
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **fetchhead_ref_out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url);
+
+int git_fetchhead_ref_cmp(const void *a, const void *b);
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs);
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "filebuf.h"
+#include "fileops.h"
+
+static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
+
+enum buferr_t {
+ BUFERR_OK = 0,
+ BUFERR_WRITE,
+ BUFERR_ZLIB,
+ BUFERR_MEM
+};
+
+#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; }
+
+static int verify_last_error(git_filebuf *file)
+{
+ switch (file->last_error) {
+ case BUFERR_WRITE:
+ giterr_set(GITERR_OS, "Failed to write out file");
+ return -1;
+
+ case BUFERR_MEM:
+ giterr_set_oom();
+ return -1;
+
+ case BUFERR_ZLIB:
+ giterr_set(GITERR_ZLIB,
+ "Buffer error when writing out ZLib data");
+ return -1;
+
+ default:
+ return 0;
+ }
+}
+
+static int lock_file(git_filebuf *file, int flags, mode_t mode)
+{
+ if (git_path_exists(file->path_lock) == true) {
+ if (flags & GIT_FILEBUF_FORCE)
+ p_unlink(file->path_lock);
+ else {
+ giterr_clear(); /* actual OS error code just confuses */
+ giterr_set(GITERR_OS,
+ "Failed to lock file '%s' for writing", file->path_lock);
+ return GIT_ELOCKED;
+ }
+ }
+
+ /* create path to the file buffer is required */
+ if (flags & GIT_FILEBUF_FORCE) {
+ /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
+ file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode);
+ } else {
+ file->fd = git_futils_creat_locked(file->path_lock, mode);
+ }
+
+ if (file->fd < 0)
+ return file->fd;
+
+ file->fd_is_open = true;
+
+ if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) {
+ git_file source;
+ char buffer[FILEIO_BUFSIZE];
+ ssize_t read_bytes;
+ int error = 0;
+
+ source = p_open(file->path_original, O_RDONLY);
+ if (source < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to open file '%s' for reading",
+ file->path_original);
+ return -1;
+ }
+
+ while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
+ if ((error = p_write(file->fd, buffer, read_bytes)) < 0)
+ break;
+ if (file->compute_digest)
+ git_hash_update(&file->digest, buffer, read_bytes);
+ }
+
+ p_close(source);
+
+ if (read_bytes < 0) {
+ giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original);
+ return -1;
+ } else if (error < 0) {
+ giterr_set(GITERR_OS, "Failed to write file '%s'", file->path_lock);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void git_filebuf_cleanup(git_filebuf *file)
+{
+ if (file->fd_is_open && file->fd >= 0)
+ p_close(file->fd);
+
+ if (file->created_lock && !file->did_rename && file->path_lock && git_path_exists(file->path_lock))
+ p_unlink(file->path_lock);
+
+ if (file->compute_digest) {
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+ }
+
+ if (file->buffer)
+ git__free(file->buffer);
+
+ /* use the presence of z_buf to decide if we need to deflateEnd */
+ if (file->z_buf) {
+ git__free(file->z_buf);
+ deflateEnd(&file->zs);
+ }
+
+ if (file->path_original)
+ git__free(file->path_original);
+ if (file->path_lock)
+ git__free(file->path_lock);
+
+ memset(file, 0x0, sizeof(git_filebuf));
+ file->fd = -1;
+}
+
+GIT_INLINE(int) flush_buffer(git_filebuf *file)
+{
+ int result = file->write(file, file->buffer, file->buf_pos);
+ file->buf_pos = 0;
+ return result;
+}
+
+int git_filebuf_flush(git_filebuf *file)
+{
+ return flush_buffer(file);
+}
+
+static int write_normal(git_filebuf *file, void *source, size_t len)
+{
+ if (len > 0) {
+ if (p_write(file->fd, (void *)source, len) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
+
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
+ }
+
+ return 0;
+}
+
+static int write_deflate(git_filebuf *file, void *source, size_t len)
+{
+ z_stream *zs = &file->zs;
+
+ if (len > 0 || file->flush_mode == Z_FINISH) {
+ zs->next_in = source;
+ zs->avail_in = (uInt)len;
+
+ do {
+ size_t have;
+
+ zs->next_out = file->z_buf;
+ zs->avail_out = (uInt)file->buf_size;
+
+ if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) {
+ file->last_error = BUFERR_ZLIB;
+ return -1;
+ }
+
+ have = file->buf_size - (size_t)zs->avail_out;
+
+ if (p_write(file->fd, file->z_buf, have) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
+
+ } while (zs->avail_out == 0);
+
+ assert(zs->avail_in == 0);
+
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
+ }
+
+ return 0;
+}
+
+#define MAX_SYMLINK_DEPTH 5
+
+static int resolve_symlink(git_buf *out, const char *path)
+{
+ int i, error, root;
+ ssize_t ret;
+ struct stat st;
+ git_buf curpath = GIT_BUF_INIT, target = GIT_BUF_INIT;
+
+ if ((error = git_buf_grow(&target, GIT_PATH_MAX + 1)) < 0 ||
+ (error = git_buf_puts(&curpath, path)) < 0)
+ return error;
+
+ for (i = 0; i < MAX_SYMLINK_DEPTH; i++) {
+ error = p_lstat(curpath.ptr, &st);
+ if (error < 0 && errno == ENOENT) {
+ error = git_buf_puts(out, curpath.ptr);
+ goto cleanup;
+ }
+
+ if (error < 0) {
+ giterr_set(GITERR_OS, "failed to stat '%s'", curpath.ptr);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (!S_ISLNK(st.st_mode)) {
+ error = git_buf_puts(out, curpath.ptr);
+ goto cleanup;
+ }
+
+ ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX);
+ if (ret < 0) {
+ giterr_set(GITERR_OS, "failed to read symlink '%s'", curpath.ptr);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (ret == GIT_PATH_MAX) {
+ giterr_set(GITERR_INVALID, "symlink target too long");
+ error = -1;
+ goto cleanup;
+ }
+
+ /* readlink(2) won't NUL-terminate for us */
+ target.ptr[ret] = '\0';
+ target.size = ret;
+
+ root = git_path_root(target.ptr);
+ if (root >= 0) {
+ if ((error = git_buf_puts(&curpath, target.ptr)) < 0)
+ goto cleanup;
+ } else {
+ git_buf dir = GIT_BUF_INIT;
+
+ if ((error = git_path_dirname_r(&dir, curpath.ptr)) < 0)
+ goto cleanup;
+
+ git_buf_swap(&curpath, &dir);
+ git_buf_free(&dir);
+
+ if ((error = git_path_apply_relative(&curpath, target.ptr)) < 0)
+ goto cleanup;
+ }
+ }
+
+ giterr_set(GITERR_INVALID, "maximum symlink depth reached");
+ error = -1;
+
+cleanup:
+ git_buf_free(&curpath);
+ git_buf_free(&target);
+ return error;
+}
+
+int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode)
+{
+ return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE);
+}
+
+int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size)
+{
+ int compression, error = -1;
+ size_t path_len, alloc_len;
+
+ /* opening an already open buffer is a programming error;
+ * assert that this never happens instead of returning
+ * an error code */
+ assert(file && path && file->buffer == NULL);
+
+ memset(file, 0x0, sizeof(git_filebuf));
+
+ if (flags & GIT_FILEBUF_DO_NOT_BUFFER)
+ file->do_not_buffer = true;
+
+ file->buf_size = size;
+ file->buf_pos = 0;
+ file->fd = -1;
+ file->last_error = BUFERR_OK;
+
+ /* Allocate the main cache buffer */
+ if (!file->do_not_buffer) {
+ file->buffer = git__malloc(file->buf_size);
+ GITERR_CHECK_ALLOC(file->buffer);
+ }
+
+ /* If we are hashing on-write, allocate a new hash context */
+ if (flags & GIT_FILEBUF_HASH_CONTENTS) {
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest) < 0)
+ goto cleanup;
+ }
+
+ compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
+
+ /* If we are deflating on-write, */
+ if (compression != 0) {
+ /* Initialize the ZLib stream */
+ if (deflateInit(&file->zs, compression) != Z_OK) {
+ giterr_set(GITERR_ZLIB, "Failed to initialize zlib");
+ goto cleanup;
+ }
+
+ /* Allocate the Zlib cache buffer */
+ file->z_buf = git__malloc(file->buf_size);
+ GITERR_CHECK_ALLOC(file->z_buf);
+
+ /* Never flush */
+ file->flush_mode = Z_NO_FLUSH;
+ file->write = &write_deflate;
+ } else {
+ file->write = &write_normal;
+ }
+
+ /* If we are writing to a temp file */
+ if (flags & GIT_FILEBUF_TEMPORARY) {
+ git_buf tmp_path = GIT_BUF_INIT;
+
+ /* Open the file as temporary for locking */
+ file->fd = git_futils_mktmp(&tmp_path, path, mode);
+
+ if (file->fd < 0) {
+ git_buf_free(&tmp_path);
+ goto cleanup;
+ }
+ file->fd_is_open = true;
+ file->created_lock = true;
+
+ /* No original path */
+ file->path_original = NULL;
+ file->path_lock = git_buf_detach(&tmp_path);
+ GITERR_CHECK_ALLOC(file->path_lock);
+ } else {
+ git_buf resolved_path = GIT_BUF_INIT;
+
+ if ((error = resolve_symlink(&resolved_path, path)) < 0)
+ goto cleanup;
+
+ /* Save the original path of the file */
+ path_len = resolved_path.size;
+ file->path_original = git_buf_detach(&resolved_path);
+
+ /* create the locking path by appending ".lock" to the original */
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH);
+ file->path_lock = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(file->path_lock);
+
+ memcpy(file->path_lock, file->path_original, path_len);
+ memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
+
+ if (git_path_isdir(file->path_original)) {
+ giterr_set(GITERR_FILESYSTEM, "path '%s' is a directory", file->path_original);
+ error = GIT_EDIRECTORY;
+ goto cleanup;
+ }
+
+ /* open the file for locking */
+ if ((error = lock_file(file, flags, mode)) < 0)
+ goto cleanup;
+
+ file->created_lock = true;
+ }
+
+ return 0;
+
+cleanup:
+ git_filebuf_cleanup(file);
+ return error;
+}
+
+int git_filebuf_hash(git_oid *oid, git_filebuf *file)
+{
+ assert(oid && file && file->compute_digest);
+
+ flush_buffer(file);
+
+ if (verify_last_error(file) < 0)
+ return -1;
+
+ git_hash_final(oid, &file->digest);
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+
+ return 0;
+}
+
+int git_filebuf_commit_at(git_filebuf *file, const char *path)
+{
+ git__free(file->path_original);
+ file->path_original = git__strdup(path);
+ GITERR_CHECK_ALLOC(file->path_original);
+
+ return git_filebuf_commit(file);
+}
+
+int git_filebuf_commit(git_filebuf *file)
+{
+ /* temporary files cannot be committed */
+ assert(file && file->path_original);
+
+ file->flush_mode = Z_FINISH;
+ flush_buffer(file);
+
+ if (verify_last_error(file) < 0)
+ goto on_error;
+
+ file->fd_is_open = false;
+
+ if (p_close(file->fd) < 0) {
+ giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock);
+ goto on_error;
+ }
+
+ file->fd = -1;
+
+ if (p_rename(file->path_lock, file->path_original) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original);
+ goto on_error;
+ }
+
+ file->did_rename = true;
+
+ git_filebuf_cleanup(file);
+ return 0;
+
+on_error:
+ git_filebuf_cleanup(file);
+ return -1;
+}
+
+GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
+{
+ memcpy(file->buffer + file->buf_pos, buf, len);
+ file->buf_pos += len;
+}
+
+int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
+{
+ const unsigned char *buf = buff;
+
+ ENSURE_BUF_OK(file);
+
+ if (file->do_not_buffer)
+ return file->write(file, (void *)buff, len);
+
+ for (;;) {
+ size_t space_left = file->buf_size - file->buf_pos;
+
+ /* cache if it's small */
+ if (space_left > len) {
+ add_to_cache(file, buf, len);
+ return 0;
+ }
+
+ add_to_cache(file, buf, space_left);
+ if (flush_buffer(file) < 0)
+ return -1;
+
+ len -= space_left;
+ buf += space_left;
+ }
+}
+
+int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
+{
+ size_t space_left = file->buf_size - file->buf_pos;
+
+ *buffer = NULL;
+
+ ENSURE_BUF_OK(file);
+
+ if (len > file->buf_size) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ if (space_left <= len) {
+ if (flush_buffer(file) < 0)
+ return -1;
+ }
+
+ *buffer = (file->buffer + file->buf_pos);
+ file->buf_pos += len;
+
+ return 0;
+}
+
+int git_filebuf_printf(git_filebuf *file, const char *format, ...)
+{
+ va_list arglist;
+ size_t space_left, len, alloclen;
+ int written, res;
+ char *tmp_buffer;
+
+ ENSURE_BUF_OK(file);
+
+ space_left = file->buf_size - file->buf_pos;
+
+ do {
+ va_start(arglist, format);
+ written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
+ va_end(arglist);
+
+ if (written < 0) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ len = written;
+ if (len + 1 <= space_left) {
+ file->buf_pos += len;
+ return 0;
+ }
+
+ if (flush_buffer(file) < 0)
+ return -1;
+
+ space_left = file->buf_size - file->buf_pos;
+
+ } while (len + 1 <= space_left);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) ||
+ !(tmp_buffer = git__malloc(alloclen))) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ va_start(arglist, format);
+ written = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
+ va_end(arglist);
+
+ if (written < 0) {
+ git__free(tmp_buffer);
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ res = git_filebuf_write(file, tmp_buffer, len);
+ git__free(tmp_buffer);
+
+ return res;
+}
+
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)
+{
+ int res;
+ struct stat st;
+
+ if (file->fd_is_open)
+ res = p_fstat(file->fd, &st);
+ else
+ res = p_stat(file->path_original, &st);
+
+ if (res < 0) {
+ giterr_set(GITERR_OS, "Could not get stat info for '%s'",
+ file->path_original);
+ return res;
+ }
+
+ if (mtime)
+ *mtime = st.st_mtime;
+ if (size)
+ *size = (size_t)st.st_size;
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_filebuf_h__
+#define INCLUDE_filebuf_h__
+
+#include "fileops.h"
+#include "hash.h"
+#include <zlib.h>
+
+#ifdef GIT_THREADS
+# define GIT_FILEBUF_THREADS
+#endif
+
+#define GIT_FILEBUF_HASH_CONTENTS (1 << 0)
+#define GIT_FILEBUF_APPEND (1 << 2)
+#define GIT_FILEBUF_FORCE (1 << 3)
+#define GIT_FILEBUF_TEMPORARY (1 << 4)
+#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
+#define GIT_FILEBUF_DEFLATE_SHIFT (6)
+
+#define GIT_FILELOCK_EXTENSION ".lock\0"
+#define GIT_FILELOCK_EXTLENGTH 6
+
+typedef struct git_filebuf git_filebuf;
+struct git_filebuf {
+ char *path_original;
+ char *path_lock;
+
+ int (*write)(git_filebuf *file, void *source, size_t len);
+
+ bool compute_digest;
+ git_hash_ctx digest;
+
+ unsigned char *buffer;
+ unsigned char *z_buf;
+
+ z_stream zs;
+ int flush_mode;
+
+ size_t buf_size, buf_pos;
+ git_file fd;
+ bool fd_is_open;
+ bool created_lock;
+ bool did_rename;
+ bool do_not_buffer;
+ int last_error;
+};
+
+#define GIT_FILEBUF_INIT {0}
+
+/*
+ * The git_filebuf object lifecycle is:
+ * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT.
+ *
+ * - Call git_filebuf_open() to initialize the filebuf for use.
+ *
+ * - Make as many calls to git_filebuf_write(), git_filebuf_printf(),
+ * git_filebuf_reserve() as you like. The error codes for these
+ * functions don't need to be checked. They are stored internally
+ * by the file buffer.
+ *
+ * - While you are writing, you may call git_filebuf_hash() to get
+ * the hash of all you have written so far. This function will
+ * fail if any of the previous writes to the buffer failed.
+ *
+ * - To close the git_filebuf, you may call git_filebuf_commit() or
+ * git_filebuf_commit_at() to save the file, or
+ * git_filebuf_cleanup() to abandon the file. All of these will
+ * free the git_filebuf object. Likewise, all of these will fail
+ * if any of the previous writes to the buffer failed, and set
+ * an error code accordingly.
+ */
+int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len);
+int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len);
+int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
+
+int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode);
+int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size);
+int git_filebuf_commit(git_filebuf *lock);
+int git_filebuf_commit_at(git_filebuf *lock, const char *path);
+void git_filebuf_cleanup(git_filebuf *lock);
+int git_filebuf_hash(git_oid *oid, git_filebuf *file);
+int git_filebuf_flush(git_filebuf *file);
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "fileops.h"
+#include "global.h"
+#include "strmap.h"
+#include <ctype.h>
+#if GIT_WIN32
+#include "win32/findfile.h"
+#endif
+
+GIT__USE_STRMAP
+
+int git_futils_mkpath2file(const char *file_path, const mode_t mode)
+{
+ return git_futils_mkdir(
+ file_path, mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
+}
+
+int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode)
+{
+ int fd;
+ mode_t mask;
+
+ p_umask(mask = p_umask(0));
+
+ git_buf_sets(path_out, filename);
+ git_buf_puts(path_out, "_git2_XXXXXX");
+
+ if (git_buf_oom(path_out))
+ return -1;
+
+ if ((fd = p_mkstemp(path_out->ptr)) < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to create temporary file '%s'", path_out->ptr);
+ return -1;
+ }
+
+ if (p_chmod(path_out->ptr, (mode & ~mask))) {
+ giterr_set(GITERR_OS,
+ "Failed to set permissions on file '%s'", path_out->ptr);
+ return -1;
+ }
+
+ return fd;
+}
+
+int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
+{
+ int fd;
+
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
+
+ fd = p_creat(path, mode);
+ if (fd < 0) {
+ giterr_set(GITERR_OS, "Failed to create file '%s'", path);
+ return -1;
+ }
+
+ return fd;
+}
+
+int git_futils_creat_locked(const char *path, const mode_t mode)
+{
+ int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC |
+ O_EXCL | O_BINARY | O_CLOEXEC, mode);
+
+ if (fd < 0) {
+ int error = errno;
+ giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
+ switch (error) {
+ case EEXIST:
+ return GIT_ELOCKED;
+ case ENOENT:
+ return GIT_ENOTFOUND;
+ default:
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
+{
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
+
+ return git_futils_creat_locked(path, mode);
+}
+
+int git_futils_open_ro(const char *path)
+{
+ int fd = p_open(path, O_RDONLY);
+ if (fd < 0)
+ return git_path_set_error(errno, path, "open");
+ return fd;
+}
+
+git_off_t git_futils_filesize(git_file fd)
+{
+ struct stat sb;
+
+ if (p_fstat(fd, &sb)) {
+ giterr_set(GITERR_OS, "Failed to stat file descriptor");
+ return -1;
+ }
+
+ return sb.st_size;
+}
+
+mode_t git_futils_canonical_mode(mode_t raw_mode)
+{
+ if (S_ISREG(raw_mode))
+ return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
+ else if (S_ISLNK(raw_mode))
+ return S_IFLNK;
+ else if (S_ISGITLINK(raw_mode))
+ return S_IFGITLINK;
+ else if (S_ISDIR(raw_mode))
+ return S_IFDIR;
+ else
+ return 0;
+}
+
+int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
+{
+ ssize_t read_size = 0;
+ size_t alloc_len;
+
+ git_buf_clear(buf);
+
+ if (!git__is_ssizet(len)) {
+ giterr_set(GITERR_INVALID, "Read too large.");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
+ if (git_buf_grow(buf, alloc_len) < 0)
+ return -1;
+
+ /* p_read loops internally to read len bytes */
+ read_size = p_read(fd, buf->ptr, len);
+
+ if (read_size != (ssize_t)len) {
+ giterr_set(GITERR_OS, "Failed to read descriptor");
+ git_buf_free(buf);
+ return -1;
+ }
+
+ buf->ptr[read_size] = '\0';
+ buf->size = read_size;
+
+ return 0;
+}
+
+int git_futils_readbuffer_updated(
+ git_buf *out, const char *path, git_oid *checksum, int *updated)
+{
+ int error;
+ git_file fd;
+ struct stat st;
+ git_buf buf = GIT_BUF_INIT;
+ git_oid checksum_new;
+
+ assert(out && path && *path);
+
+ if (updated != NULL)
+ *updated = 0;
+
+ if (p_stat(path, &st) < 0)
+ return git_path_set_error(errno, path, "stat");
+
+
+ if (S_ISDIR(st.st_mode)) {
+ giterr_set(GITERR_INVALID, "requested file is a directory");
+ return GIT_ENOTFOUND;
+ }
+
+ if (!git__is_sizet(st.st_size+1)) {
+ giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
+ return -1;
+ }
+
+ if ((fd = git_futils_open_ro(path)) < 0)
+ return fd;
+
+ if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
+ p_close(fd);
+ return -1;
+ }
+
+ p_close(fd);
+
+ if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
+ git_buf_free(&buf);
+ return error;
+ }
+
+ /*
+ * If we were given a checksum, we only want to use it if it's different
+ */
+ if (checksum && !git_oid__cmp(checksum, &checksum_new)) {
+ git_buf_free(&buf);
+ if (updated)
+ *updated = 0;
+
+ return 0;
+ }
+
+ /*
+ * If we're here, the file did change, or the user didn't have an old version
+ */
+ if (checksum)
+ git_oid_cpy(checksum, &checksum_new);
+
+ if (updated != NULL)
+ *updated = 1;
+
+ git_buf_swap(out, &buf);
+ git_buf_free(&buf);
+
+ return 0;
+}
+
+int git_futils_readbuffer(git_buf *buf, const char *path)
+{
+ return git_futils_readbuffer_updated(buf, path, NULL, NULL);
+}
+
+int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int flags, mode_t mode)
+{
+ int fd, error = 0;
+
+ if (flags <= 0)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+ if (!mode)
+ mode = GIT_FILEMODE_BLOB;
+
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ return error;
+ }
+
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ return error;
+}
+
+int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
+{
+ if (git_futils_mkpath2file(to, dirmode) < 0)
+ return -1;
+
+ if (p_rename(from, to) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
+{
+ return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
+}
+
+int git_futils_mmap_ro_file(git_map *out, const char *path)
+{
+ git_file fd = git_futils_open_ro(path);
+ git_off_t len;
+ int result;
+
+ if (fd < 0)
+ return fd;
+
+ len = git_futils_filesize(fd);
+ if (!git__is_sizet(len)) {
+ giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
+ return -1;
+ }
+
+ result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
+ p_close(fd);
+ return result;
+}
+
+void git_futils_mmap_free(git_map *out)
+{
+ p_munmap(out);
+}
+
+GIT_INLINE(int) mkdir_validate_dir(
+ const char *path,
+ struct stat *st,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ /* with exclusive create, existing dir is an error */
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_FILESYSTEM,
+ "Failed to make directory '%s': directory exists", path);
+ return GIT_EEXISTS;
+ }
+
+ if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
+ (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
+ if (p_unlink(path) < 0) {
+ giterr_set(GITERR_OS, "Failed to remove %s '%s'",
+ S_ISLNK(st->st_mode) ? "symlink" : "file", path);
+ return GIT_EEXISTS;
+ }
+
+ opts->perfdata.mkdir_calls++;
+
+ if (p_mkdir(path, mode) < 0) {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (S_ISLNK(st->st_mode)) {
+ /* Re-stat the target, make sure it's a directory */
+ opts->perfdata.stat_calls++;
+
+ if (p_stat(path, st) < 0) {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (!S_ISDIR(st->st_mode)) {
+ giterr_set(GITERR_FILESYSTEM,
+ "Failed to make directory '%s': directory exists", path);
+ return GIT_EEXISTS;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) mkdir_validate_mode(
+ const char *path,
+ struct stat *st,
+ bool terminal_path,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
+ (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {
+
+ opts->perfdata.chmod_calls++;
+
+ if (p_chmod(path, mode) < 0) {
+ giterr_set(GITERR_OS, "failed to set permissions on '%s'", path);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) mkdir_canonicalize(
+ git_buf *path,
+ uint32_t flags)
+{
+ ssize_t root_len;
+
+ if (path->size == 0) {
+ giterr_set(GITERR_OS, "attempt to create empty path");
+ return -1;
+ }
+
+ /* Trim trailing slashes (except the root) */
+ if ((root_len = git_path_root(path->ptr)) < 0)
+ root_len = 0;
+ else
+ root_len++;
+
+ while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
+ path->ptr[--path->size] = '\0';
+
+ /* if we are not supposed to made the last element, truncate it */
+ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
+ git_path_dirname_r(path, path->ptr);
+ flags |= GIT_MKDIR_SKIP_LAST;
+ }
+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
+ git_path_dirname_r(path, path->ptr);
+ }
+
+ /* We were either given the root path (or trimmed it to
+ * the root), we don't have anything to do.
+ */
+ if (path->size <= (size_t)root_len)
+ git_buf_clear(path);
+
+ return 0;
+}
+
+int git_futils_mkdir(
+ const char *path,
+ mode_t mode,
+ uint32_t flags)
+{
+ git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT;
+ const char *relative;
+ struct git_futils_mkdir_options opts = { 0 };
+ struct stat st;
+ size_t depth = 0;
+ int len = 0, root_len, error;
+
+ if ((error = git_buf_puts(&make_path, path)) < 0 ||
+ (error = mkdir_canonicalize(&make_path, flags)) < 0 ||
+ (error = git_buf_puts(&parent_path, make_path.ptr)) < 0 ||
+ make_path.size == 0)
+ goto done;
+
+ root_len = git_path_root(make_path.ptr);
+
+ /* find the first parent directory that exists. this will be used
+ * as the base to dirname_relative.
+ */
+ for (relative = make_path.ptr; parent_path.size; ) {
+ error = p_lstat(parent_path.ptr, &st);
+
+ if (error == 0) {
+ break;
+ } else if (errno != ENOENT) {
+ giterr_set(GITERR_OS, "failed to stat '%s'", parent_path.ptr);
+ goto done;
+ }
+
+ depth++;
+
+ /* examine the parent of the current path */
+ if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
+ error = len;
+ goto done;
+ }
+
+ assert(len);
+
+ /* we've walked all the given path's parents and it's either relative
+ * or rooted. either way, give up and make the entire path.
+ */
+ if ((len == 1 && parent_path.ptr[0] == '.') || len == root_len+1) {
+ relative = make_path.ptr;
+ break;
+ }
+
+ relative = make_path.ptr + len + 1;
+
+ /* not recursive? just make this directory relative to its parent. */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ break;
+ }
+
+ /* we found an item at the location we're trying to create,
+ * validate it.
+ */
+ if (depth == 0) {
+ error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
+
+ if (!error)
+ error = mkdir_validate_mode(
+ make_path.ptr, &st, true, mode, flags, &opts);
+
+ goto done;
+ }
+
+ /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
+ * canonicalizing `make_path`.
+ */
+ flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);
+
+ error = git_futils_mkdir_relative(relative,
+ parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);
+
+done:
+ git_buf_free(&make_path);
+ git_buf_free(&parent_path);
+ return error;
+}
+
+int git_futils_mkdir_r(const char *path, const mode_t mode)
+{
+ return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
+}
+
+int git_futils_mkdir_relative(
+ const char *relative_path,
+ const char *base,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ git_buf make_path = GIT_BUF_INIT;
+ ssize_t root = 0, min_root_len;
+ char lastch = '/', *tail;
+ struct stat st;
+ struct git_futils_mkdir_options empty_opts = {0};
+ int error;
+
+ if (!opts)
+ opts = &empty_opts;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
+ return -1;
+
+ if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
+ make_path.size == 0)
+ goto done;
+
+ /* if we are not supposed to make the whole path, reset root */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ root = git_buf_rfind(&make_path, '/');
+
+ /* advance root past drive name or network mount prefix */
+ min_root_len = git_path_root(make_path.ptr);
+ if (root < min_root_len)
+ root = min_root_len;
+ while (root >= 0 && make_path.ptr[root] == '/')
+ ++root;
+
+ /* clip root to make_path length */
+ if (root > (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
+ if (root < 0)
+ root = 0;
+
+ /* walk down tail of path making each directory */
+ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
+ bool mkdir_attempted = false;
+
+ /* advance tail to include next path component */
+ while (*tail == '/')
+ tail++;
+ while (*tail && *tail != '/')
+ tail++;
+
+ /* truncate path at next component */
+ lastch = *tail;
+ *tail = '\0';
+ st.st_mode = 0;
+
+ if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
+ continue;
+
+ /* See what's going on with this path component */
+ opts->perfdata.stat_calls++;
+
+retry_lstat:
+ if (p_lstat(make_path.ptr, &st) < 0) {
+ if (mkdir_attempted || errno != ENOENT) {
+ giterr_set(GITERR_OS, "Cannot access component in path '%s'", make_path.ptr);
+ error = -1;
+ goto done;
+ }
+
+ giterr_clear();
+ opts->perfdata.mkdir_calls++;
+ mkdir_attempted = true;
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ if (errno == EEXIST)
+ goto retry_lstat;
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
+ error = -1;
+ goto done;
+ }
+ } else {
+ if ((error = mkdir_validate_dir(
+ make_path.ptr, &st, mode, flags, opts)) < 0)
+ goto done;
+ }
+
+ /* chmod if requested and necessary */
+ if ((error = mkdir_validate_mode(
+ make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
+ goto done;
+
+ if (opts->dir_map && opts->pool) {
+ char *cache_path;
+ size_t alloc_size;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
+ if (!git__is_uint32(alloc_size))
+ return -1;
+ cache_path = git_pool_malloc(opts->pool, (uint32_t)alloc_size);
+ GITERR_CHECK_ALLOC(cache_path);
+
+ memcpy(cache_path, make_path.ptr, make_path.size + 1);
+
+ git_strmap_insert(opts->dir_map, cache_path, cache_path, error);
+ if (error < 0)
+ goto done;
+ }
+ }
+
+ error = 0;
+
+ /* check that full path really is a directory if requested & needed */
+ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ lastch != '\0') {
+ opts->perfdata.stat_calls++;
+
+ if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ giterr_set(GITERR_OS, "Path is not a directory '%s'",
+ make_path.ptr);
+ error = GIT_ENOTFOUND;
+ }
+ }
+
+done:
+ git_buf_free(&make_path);
+ return error;
+}
+
+typedef struct {
+ const char *base;
+ size_t baselen;
+ uint32_t flags;
+ int depth;
+} futils__rmdir_data;
+
+#define FUTILS_MAX_DEPTH 100
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
+{
+ if (filemsg)
+ giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s",
+ path, filemsg);
+ else
+ giterr_set(GITERR_OS, "Could not remove directory '%s'", path);
+
+ return -1;
+}
+
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
+
+ while (error == GIT_ENOTFOUND) {
+ git_buf_rtruncate_at_char(path, '/');
+
+ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
+ error = 0;
+ else if (p_lstat_posixly(path->ptr, &st) == 0) {
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ error = p_unlink(path->ptr);
+ else if (!S_ISDIR(st.st_mode))
+ error = -1; /* fail to remove non-regular file */
+ } else if (errno != ENOTDIR)
+ error = -1;
+ }
+
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
+
+ return error;
+}
+
+static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
+{
+ int error = 0;
+ futils__rmdir_data *data = opaque;
+ struct stat st;
+
+ if (data->depth > FUTILS_MAX_DEPTH)
+ error = futils__error_cannot_rmdir(
+ path->ptr, "directory nesting too deep");
+
+ else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ error = 0;
+ else if (errno == ENOTDIR) {
+ /* asked to remove a/b/c/d/e and a/b is a normal file */
+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
+ error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
+ }
+ else
+ error = git_path_set_error(errno, path->ptr, "rmdir");
+ }
+
+ else if (S_ISDIR(st.st_mode)) {
+ data->depth++;
+
+ error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
+
+ data->depth--;
+
+ if (error < 0)
+ return error;
+
+ if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
+ return error;
+
+ if ((error = p_rmdir(path->ptr)) < 0) {
+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
+ (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
+ error = 0;
+ else
+ error = git_path_set_error(errno, path->ptr, "rmdir");
+ }
+ }
+
+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
+ if (p_unlink(path->ptr) < 0)
+ error = git_path_set_error(errno, path->ptr, "remove");
+ }
+
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ error = futils__error_cannot_rmdir(path->ptr, "still present");
+
+ return error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, const char *path)
+{
+ futils__rmdir_data *data = opaque;
+ int error = 0;
+
+ if (strlen(path) <= data->baselen)
+ error = GIT_ITEROVER;
+
+ else if (p_rmdir(path) < 0) {
+ int en = errno;
+
+ if (en == ENOENT || en == ENOTDIR) {
+ /* do nothing */
+ } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
+ error = GIT_ITEROVER;
+ } else {
+ error = git_path_set_error(errno, path, "rmdir");
+ }
+ }
+
+ return error;
+}
+
+int git_futils_rmdir_r(
+ const char *path, const char *base, uint32_t flags)
+{
+ int error;
+ git_buf fullpath = GIT_BUF_INIT;
+ futils__rmdir_data data;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
+ return -1;
+
+ memset(&data, 0, sizeof(data));
+ data.base = base ? base : "";
+ data.baselen = base ? strlen(base) : 0;
+ data.flags = flags;
+
+ error = futils__rmdir_recurs_foreach(&data, &fullpath);
+
+ /* remove now-empty parents if requested */
+ if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
+ error = git_path_walk_up(
+ &fullpath, base, futils__rmdir_empty_parent, &data);
+
+ if (error == GIT_ITEROVER) {
+ giterr_clear();
+ error = 0;
+ }
+
+ git_buf_free(&fullpath);
+
+ return error;
+}
+
+int git_futils_fake_symlink(const char *old, const char *new)
+{
+ int retcode = GIT_ERROR;
+ int fd = git_futils_creat_withpath(new, 0755, 0644);
+ if (fd >= 0) {
+ retcode = p_write(fd, old, strlen(old));
+ p_close(fd);
+ }
+ return retcode;
+}
+
+static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
+{
+ int error = 0;
+ char buffer[FILEIO_BUFSIZE];
+ ssize_t len = 0;
+
+ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
+ /* p_write() does not have the same semantics as write(). It loops
+ * internally and will return 0 when it has completed writing.
+ */
+ error = p_write(ofd, buffer, len);
+
+ if (len < 0) {
+ giterr_set(GITERR_OS, "Read error while copying file");
+ error = (int)len;
+ }
+
+ if (error < 0)
+ giterr_set(GITERR_OS, "write error while copying file");
+
+ if (close_fd_when_done) {
+ p_close(ifd);
+ p_close(ofd);
+ }
+
+ return error;
+}
+
+int git_futils_cp(const char *from, const char *to, mode_t filemode)
+{
+ int ifd, ofd;
+
+ if ((ifd = git_futils_open_ro(from)) < 0)
+ return ifd;
+
+ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
+ p_close(ifd);
+ return git_path_set_error(errno, to, "open for writing");
+ }
+
+ return cp_by_fd(ifd, ofd, true);
+}
+
+int git_futils_touch(const char *path, time_t *when)
+{
+ struct p_timeval times[2];
+ int ret;
+
+ times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL);
+ times[0].tv_usec = times[1].tv_usec = 0;
+
+ ret = p_utimes(path, times);
+
+ return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0;
+}
+
+static int cp_link(const char *from, const char *to, size_t link_size)
+{
+ int error = 0;
+ ssize_t read_len;
+ char *link_data;
+ size_t alloc_size;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
+ link_data = git__malloc(alloc_size);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(from, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
+ error = -1;
+ }
+ else {
+ link_data[read_len] = '\0';
+
+ if (p_symlink(link_data, to) < 0) {
+ giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
+ link_data, to);
+ error = -1;
+ }
+ }
+
+ git__free(link_data);
+ return error;
+}
+
+typedef struct {
+ const char *to_root;
+ git_buf to;
+ ssize_t from_prefix;
+ uint32_t flags;
+ uint32_t mkdir_flags;
+ mode_t dirmode;
+} cp_r_info;
+
+#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
+
+static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
+{
+ int error = 0;
+
+ /* create root directory the first time we need to create a directory */
+ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
+ error = git_futils_mkdir(
+ info->to_root, info->dirmode,
+ (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
+
+ info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
+ }
+
+ /* create directory with root as base to prevent excess chmods */
+ if (!error)
+ error = git_futils_mkdir_relative(
+ from->ptr + info->from_prefix, info->to_root,
+ info->dirmode, info->mkdir_flags, NULL);
+
+ return error;
+}
+
+static int _cp_r_callback(void *ref, git_buf *from)
+{
+ int error = 0;
+ cp_r_info *info = ref;
+ struct stat from_st, to_st;
+ bool exists = false;
+
+ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
+ from->ptr[git_path_basename_offset(from)] == '.')
+ return 0;
+
+ if ((error = git_buf_joinpath(
+ &info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
+ return error;
+
+ if (!(error = git_path_lstat(info->to.ptr, &to_st)))
+ exists = true;
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ else {
+ giterr_clear();
+ error = 0;
+ }
+
+ if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
+ return error;
+
+ if (S_ISDIR(from_st.st_mode)) {
+ mode_t oldmode = info->dirmode;
+
+ /* if we are not chmod'ing, then overwrite dirmode */
+ if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
+ info->dirmode = from_st.st_mode;
+
+ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
+ error = _cp_r_mkdir(info, from);
+
+ /* recurse onto target directory */
+ if (!error && (!exists || S_ISDIR(to_st.st_mode)))
+ error = git_path_direach(from, 0, _cp_r_callback, info);
+
+ if (oldmode != 0)
+ info->dirmode = oldmode;
+
+ return error;
+ }
+
+ if (exists) {
+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
+ return 0;
+
+ if (p_unlink(info->to.ptr) < 0) {
+ giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
+ info->to.ptr);
+ return GIT_EEXISTS;
+ }
+ }
+
+ /* Done if this isn't a regular file or a symlink */
+ if (!S_ISREG(from_st.st_mode) &&
+ (!S_ISLNK(from_st.st_mode) ||
+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
+ return 0;
+
+ /* Make container directory on demand if needed */
+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
+ (error = _cp_r_mkdir(info, from)) < 0)
+ return error;
+
+ /* make symlink or regular file */
+ if (info->flags & GIT_CPDIR_LINK_FILES) {
+ if ((error = p_link(from->ptr, info->to.ptr)) < 0)
+ giterr_set(GITERR_OS, "failed to link '%s'", from->ptr);
+ } else if (S_ISLNK(from_st.st_mode)) {
+ error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
+ } else {
+ mode_t usemode = from_st.st_mode;
+
+ if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
+ usemode = GIT_PERMS_FOR_WRITE(usemode);
+
+ error = git_futils_cp(from->ptr, info->to.ptr, usemode);
+ }
+
+ return error;
+}
+
+int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ cp_r_info info;
+
+ if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
+ return -1;
+
+ memset(&info, 0, sizeof(info));
+ info.to_root = to;
+ info.flags = flags;
+ info.dirmode = dirmode;
+ info.from_prefix = path.size;
+ git_buf_init(&info.to, 0);
+
+ /* precalculate mkdir flags */
+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
+ /* if not creating empty dirs, then use mkdir to create the path on
+ * demand right before files are copied.
+ */
+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
+ if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
+ } else {
+ /* otherwise, we will do simple mkdir as directories are encountered */
+ info.mkdir_flags =
+ ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
+ }
+
+ error = _cp_r_callback(&info, &path);
+
+ git_buf_free(&path);
+ git_buf_free(&info.to);
+
+ return error;
+}
+
+int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path)
+{
+ struct stat st;
+
+ /* if the stamp is NULL, then always reload */
+ if (stamp == NULL)
+ return 1;
+
+ if (p_stat(path, &st) < 0)
+ return GIT_ENOTFOUND;
+
+ if (stamp->mtime.tv_sec == st.st_mtime &&
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec == st.st_mtime_nsec &&
+#endif
+ stamp->size == (git_off_t)st.st_size &&
+ stamp->ino == (unsigned int)st.st_ino)
+ return 0;
+
+ stamp->mtime.tv_sec = st.st_mtime;
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec = st.st_mtime_nsec;
+#endif
+ stamp->size = (git_off_t)st.st_size;
+ stamp->ino = (unsigned int)st.st_ino;
+
+ return 1;
+}
+
+void git_futils_filestamp_set(
+ git_futils_filestamp *target, const git_futils_filestamp *source)
+{
+ assert(target);
+
+ if (source)
+ memcpy(target, source, sizeof(*target));
+ else
+ memset(target, 0, sizeof(*target));
+}
+
+
+void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st)
+{
+ if (st) {
+ stamp->mtime.tv_sec = st->st_mtime;
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec = st->st_mtime_nsec;
+#else
+ stamp->mtime.tv_nsec = 0;
+#endif
+ stamp->size = (git_off_t)st->st_size;
+ stamp->ino = (unsigned int)st->st_ino;
+ } else {
+ memset(stamp, 0, sizeof(*stamp));
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_fileops_h__
+#define INCLUDE_fileops_h__
+
+#include "common.h"
+#include "map.h"
+#include "posix.h"
+#include "path.h"
+#include "pool.h"
+#include "strmap.h"
+#include "oid.h"
+
+/**
+ * Filebuffer methods
+ *
+ * Read whole files into an in-memory buffer for processing
+ */
+extern int git_futils_readbuffer(git_buf *obj, const char *path);
+extern int git_futils_readbuffer_updated(
+ git_buf *obj, const char *path, git_oid *checksum, int *updated);
+extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
+
+extern int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int open_flags, mode_t mode);
+
+/**
+ * File utils
+ *
+ * These are custom filesystem-related helper methods. They are
+ * rather high level, and wrap the underlying POSIX methods
+ *
+ * All these methods return 0 on success,
+ * or an error code on failure and an error message is set.
+ */
+
+/**
+ * Create and open a file, while also
+ * creating all the folders in its path
+ */
+extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode);
+
+/**
+ * Create and open a process-locked file
+ */
+extern int git_futils_creat_locked(const char *path, const mode_t mode);
+
+/**
+ * Create and open a process-locked file, while
+ * also creating all the folders in its path
+ */
+extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode);
+
+/**
+ * Create a path recursively.
+ */
+extern int git_futils_mkdir_r(const char *path, const mode_t mode);
+
+/**
+ * Flags to pass to `git_futils_mkdir`.
+ *
+ * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
+ * * GIT_MKDIR_PATH says to make all components in the path.
+ * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
+ * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
+ * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
+ * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
+ * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs
+ * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs
+ *
+ * Note that the chmod options will be executed even if the directory already
+ * exists, unless GIT_MKDIR_EXCL is given.
+ */
+typedef enum {
+ GIT_MKDIR_EXCL = 1,
+ GIT_MKDIR_PATH = 2,
+ GIT_MKDIR_CHMOD = 4,
+ GIT_MKDIR_CHMOD_PATH = 8,
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_SKIP_LAST2 = 32,
+ GIT_MKDIR_VERIFY_DIR = 64,
+ GIT_MKDIR_REMOVE_FILES = 128,
+ GIT_MKDIR_REMOVE_SYMLINKS = 256,
+} git_futils_mkdir_flags;
+
+struct git_futils_mkdir_perfdata
+{
+ size_t stat_calls;
+ size_t mkdir_calls;
+ size_t chmod_calls;
+};
+
+struct git_futils_mkdir_options
+{
+ git_strmap *dir_map;
+ git_pool *pool;
+ struct git_futils_mkdir_perfdata perfdata;
+};
+
+/**
+ * Create a directory or entire path.
+ *
+ * This makes a directory (and the entire path leading up to it if requested),
+ * and optionally chmods the directory immediately after (or each part of the
+ * path if requested).
+ *
+ * @param path The path to create, relative to base.
+ * @param base Root for relative path. These directories will never be made.
+ * @param mode The mode to use for created directories.
+ * @param flags Combination of the mkdir flags above.
+ * @param opts Extended options, or null.
+ * @return 0 on success, else error code
+ */
+extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts);
+
+/**
+ * Create a directory or entire path. Similar to `git_futils_mkdir_relative`
+ * without performance data.
+ */
+extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags);
+
+/**
+ * Create all the folders required to contain
+ * the full path of a file
+ */
+extern int git_futils_mkpath2file(const char *path, const mode_t mode);
+
+/**
+ * Flags to pass to `git_futils_rmdir_r`.
+ *
+ * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty
+ * dirs and generate error if any files are found.
+ * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.
+ * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
+ * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
+ * if removing this item leaves them empty
+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
+ * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself
+ */
+typedef enum {
+ GIT_RMDIR_EMPTY_HIERARCHY = 0,
+ GIT_RMDIR_REMOVE_FILES = (1 << 0),
+ GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
+ GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
+ GIT_RMDIR_SKIP_ROOT = (1 << 4),
+} git_futils_rmdir_flags;
+
+/**
+ * Remove path and any files and directories beneath it.
+ *
+ * @param path Path to the top level directory to process.
+ * @param base Root for relative path.
+ * @param flags Combination of git_futils_rmdir_flags values
+ * @return 0 on success; -1 on error.
+ */
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
+
+/**
+ * Create and open a temporary file with a `_git2_` suffix.
+ * Writes the filename into path_out.
+ * @return On success, an open file descriptor, else an error code < 0.
+ */
+extern int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode);
+
+/**
+ * Move a file on the filesystem, create the
+ * destination path if it doesn't exist
+ */
+extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
+
+/**
+ * Copy a file
+ *
+ * The filemode will be used for the newly created file.
+ */
+extern int git_futils_cp(
+ const char *from,
+ const char *to,
+ mode_t filemode);
+
+/**
+ * Set the files atime and mtime to the given time, or the current time
+ * if `ts` is NULL.
+ */
+extern int git_futils_touch(const char *path, time_t *when);
+
+/**
+ * Flags that can be passed to `git_futils_cp_r`.
+ *
+ * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no
+ * files under them (otherwise directories will only be created lazily
+ * when a file inside them is copied).
+ * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.
+ * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.
+ * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,
+ * otherwise they are silently skipped.
+ * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`
+ * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
+ * source file to the target; with this flag, always use 0666 (or 0777 if
+ * source has exec bits set) for target.
+ * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files
+ */
+typedef enum {
+ GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
+ GIT_CPDIR_COPY_SYMLINKS = (1u << 1),
+ GIT_CPDIR_COPY_DOTFILES = (1u << 2),
+ GIT_CPDIR_OVERWRITE = (1u << 3),
+ GIT_CPDIR_CHMOD_DIRS = (1u << 4),
+ GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),
+ GIT_CPDIR_LINK_FILES = (1u << 6),
+} git_futils_cpdir_flags;
+
+/**
+ * Copy a directory tree.
+ *
+ * This copies directories and files from one root to another. You can
+ * pass a combinationof GIT_CPDIR flags as defined above.
+ *
+ * If you pass the CHMOD flag, then the dirmode will be applied to all
+ * directories that are created during the copy, overiding the natural
+ * permissions. If you do not pass the CHMOD flag, then the dirmode
+ * will actually be copied from the source files and the `dirmode` arg
+ * will be ignored.
+ */
+extern int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode);
+
+/**
+ * Open a file readonly and set error if needed.
+ */
+extern int git_futils_open_ro(const char *path);
+
+/**
+ * Get the filesize in bytes of a file
+ */
+extern git_off_t git_futils_filesize(git_file fd);
+
+#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0)
+#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644)
+#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666)
+
+#define GIT_MODE_PERMS_MASK 0777
+#define GIT_MODE_TYPE_MASK 0170000
+#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK)
+#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+
+/**
+ * Convert a mode_t from the OS to a legal git mode_t value.
+ */
+extern mode_t git_futils_canonical_mode(mode_t raw_mode);
+
+
+/**
+ * Read-only map all or part of a file into memory.
+ * When possible this function should favor a virtual memory
+ * style mapping over some form of malloc()+read(), as the
+ * data access will be random and is not likely to touch the
+ * majority of the region requested.
+ *
+ * @param out buffer to populate with the mapping information.
+ * @param fd open descriptor to configure the mapping from.
+ * @param begin first byte to map, this should be page aligned.
+ * @param len number of bytes to map.
+ * @return
+ * - 0 on success;
+ * - -1 on error.
+ */
+extern int git_futils_mmap_ro(
+ git_map *out,
+ git_file fd,
+ git_off_t begin,
+ size_t len);
+
+/**
+ * Read-only map an entire file.
+ *
+ * @param out buffer to populate with the mapping information.
+ * @param path path to file to be opened.
+ * @return
+ * - 0 on success;
+ * - GIT_ENOTFOUND if not found;
+ * - -1 on an unspecified OS related error.
+ */
+extern int git_futils_mmap_ro_file(
+ git_map *out,
+ const char *path);
+
+/**
+ * Release the memory associated with a previous memory mapping.
+ * @param map the mapping description previously configured.
+ */
+extern void git_futils_mmap_free(git_map *map);
+
+/**
+ * Create a "fake" symlink (text file containing the target path).
+ *
+ * @param new symlink file to be created
+ * @param old original symlink target
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fake_symlink(const char *new, const char *old);
+
+/**
+ * A file stamp represents a snapshot of information about a file that can
+ * be used to test if the file changes. This portable implementation is
+ * based on stat data about that file, but it is possible that OS specific
+ * versions could be implemented in the future.
+ */
+typedef struct {
+ struct timespec mtime;
+ git_off_t size;
+ unsigned int ino;
+} git_futils_filestamp;
+
+/**
+ * Compare stat information for file with reference info.
+ *
+ * This function updates the file stamp to current data for the given path
+ * and returns 0 if the file is up-to-date relative to the prior setting,
+ * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
+ * exist. This will not call giterr_set, so you must set the error if you
+ * plan to return an error.
+ *
+ * @param stamp File stamp to be checked
+ * @param path Path to stat and check if changed
+ * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat
+ */
+extern int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path);
+
+/**
+ * Set or reset file stamp data
+ *
+ * This writes the target file stamp. If the source is NULL, this will set
+ * the target stamp to values that will definitely be out of date. If the
+ * source is not NULL, this copies the source values to the target.
+ *
+ * @param tgt File stamp to write to
+ * @param src File stamp to copy from or NULL to clear the target
+ */
+extern void git_futils_filestamp_set(
+ git_futils_filestamp *tgt, const git_futils_filestamp *src);
+
+/**
+ * Set file stamp data from stat structure
+ */
+extern void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st);
+
+#endif /* INCLUDE_fileops_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "fileops.h"
+#include "hash.h"
+#include "filter.h"
+#include "repository.h"
+#include "global.h"
+#include "git2/sys/filter.h"
+#include "git2/config.h"
+#include "blob.h"
+#include "attr_file.h"
+#include "array.h"
+
+struct git_filter_source {
+ git_repository *repo;
+ const char *path;
+ git_oid oid; /* zero if unknown (which is likely) */
+ uint16_t filemode; /* zero if unknown */
+ git_filter_mode_t mode;
+ uint32_t flags;
+};
+
+typedef struct {
+ const char *filter_name;
+ git_filter *filter;
+ void *payload;
+} git_filter_entry;
+
+struct git_filter_list {
+ git_array_t(git_filter_entry) filters;
+ git_filter_source source;
+ git_buf *temp_buf;
+ char path[GIT_FLEX_ARRAY];
+};
+
+typedef struct {
+ char *filter_name;
+ git_filter *filter;
+ int priority;
+ int initialized;
+ size_t nattrs, nmatches;
+ char *attrdata;
+ const char *attrs[GIT_FLEX_ARRAY];
+} git_filter_def;
+
+static int filter_def_priority_cmp(const void *a, const void *b)
+{
+ int pa = ((const git_filter_def *)a)->priority;
+ int pb = ((const git_filter_def *)b)->priority;
+ return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
+}
+
+struct git_filter_registry {
+ git_rwlock lock;
+ git_vector filters;
+};
+
+static struct git_filter_registry filter_registry;
+
+static void git_filter_global_shutdown(void);
+
+
+static int filter_def_scan_attrs(
+ git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
+{
+ const char *start, *scan = attr_str;
+ int has_eq;
+
+ *nattr = *nmatch = 0;
+
+ if (!scan)
+ return 0;
+
+ while (*scan) {
+ while (git__isspace(*scan)) scan++;
+
+ for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
+ if (*scan == '=')
+ has_eq = 1;
+ }
+
+ if (scan > start) {
+ (*nattr)++;
+ if (has_eq || *start == '-' || *start == '+' || *start == '!')
+ (*nmatch)++;
+
+ if (has_eq)
+ git_buf_putc(attrs, '=');
+ git_buf_put(attrs, start, scan - start);
+ git_buf_putc(attrs, '\0');
+ }
+ }
+
+ return 0;
+}
+
+static void filter_def_set_attrs(git_filter_def *fdef)
+{
+ char *scan = fdef->attrdata;
+ size_t i;
+
+ for (i = 0; i < fdef->nattrs; ++i) {
+ const char *name, *value;
+
+ switch (*scan) {
+ case '=':
+ name = scan + 1;
+ for (scan++; *scan != '='; scan++) /* find '=' */;
+ *scan++ = '\0';
+ value = scan;
+ break;
+ case '-':
+ name = scan + 1; value = git_attr__false; break;
+ case '+':
+ name = scan + 1; value = git_attr__true; break;
+ case '!':
+ name = scan + 1; value = git_attr__unset; break;
+ default:
+ name = scan; value = NULL; break;
+ }
+
+ fdef->attrs[i] = name;
+ fdef->attrs[i + fdef->nattrs] = value;
+
+ scan += strlen(scan) + 1;
+ }
+}
+
+static int filter_def_name_key_check(const void *key, const void *fdef)
+{
+ const char *name =
+ fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
+ return name ? git__strcmp(key, name) : -1;
+}
+
+static int filter_def_filter_key_check(const void *key, const void *fdef)
+{
+ const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
+ return (key == filter) ? 0 : -1;
+}
+
+/* Note: callers must lock the registry before calling this function */
+static int filter_registry_insert(
+ const char *name, git_filter *filter, int priority)
+{
+ git_filter_def *fdef;
+ size_t nattr = 0, nmatch = 0, alloc_len;
+ git_buf attrs = GIT_BUF_INIT;
+
+ if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
+ return -1;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2);
+ GITERR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *));
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def));
+
+ fdef = git__calloc(1, alloc_len);
+ GITERR_CHECK_ALLOC(fdef);
+
+ fdef->filter_name = git__strdup(name);
+ GITERR_CHECK_ALLOC(fdef->filter_name);
+
+ fdef->filter = filter;
+ fdef->priority = priority;
+ fdef->nattrs = nattr;
+ fdef->nmatches = nmatch;
+ fdef->attrdata = git_buf_detach(&attrs);
+
+ filter_def_set_attrs(fdef);
+
+ if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
+ git__free(fdef->filter_name);
+ git__free(fdef->attrdata);
+ git__free(fdef);
+ return -1;
+ }
+
+ git_vector_sort(&filter_registry.filters);
+ return 0;
+}
+
+int git_filter_global_init(void)
+{
+ git_filter *crlf = NULL, *ident = NULL;
+ int error = 0;
+
+ if (git_rwlock_init(&filter_registry.lock) < 0)
+ return -1;
+
+ if ((error = git_vector_init(&filter_registry.filters, 2,
+ filter_def_priority_cmp)) < 0)
+ goto done;
+
+ if ((crlf = git_crlf_filter_new()) == NULL ||
+ filter_registry_insert(
+ GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 ||
+ (ident = git_ident_filter_new()) == NULL ||
+ filter_registry_insert(
+ GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
+ error = -1;
+
+ git__on_shutdown(git_filter_global_shutdown);
+
+done:
+ if (error) {
+ git_filter_free(crlf);
+ git_filter_free(ident);
+ }
+
+ return error;
+}
+
+static void git_filter_global_shutdown(void)
+{
+ size_t pos;
+ git_filter_def *fdef;
+
+ if (git_rwlock_wrlock(&filter_registry.lock) < 0)
+ return;
+
+ git_vector_foreach(&filter_registry.filters, pos, fdef) {
+ if (fdef->filter && fdef->filter->shutdown) {
+ fdef->filter->shutdown(fdef->filter);
+ fdef->initialized = false;
+ }
+
+ git__free(fdef->filter_name);
+ git__free(fdef->attrdata);
+ git__free(fdef);
+ }
+
+ git_vector_free(&filter_registry.filters);
+
+ git_rwlock_wrunlock(&filter_registry.lock);
+ git_rwlock_free(&filter_registry.lock);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static int filter_registry_find(size_t *pos, const char *name)
+{
+ return git_vector_search2(
+ pos, &filter_registry.filters, filter_def_name_key_check, name);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
+{
+ git_filter_def *fdef = NULL;
+
+ if (!filter_registry_find(pos, name))
+ fdef = git_vector_get(&filter_registry.filters, *pos);
+
+ return fdef;
+}
+
+
+int git_filter_register(
+ const char *name, git_filter *filter, int priority)
+{
+ int error;
+
+ assert(name && filter);
+
+ if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock filter registry");
+ return -1;
+ }
+
+ if (!filter_registry_find(NULL, name)) {
+ giterr_set(
+ GITERR_FILTER, "attempt to reregister existing filter '%s'", name);
+ error = GIT_EEXISTS;
+ goto done;
+ }
+
+ error = filter_registry_insert(name, filter, priority);
+
+done:
+ git_rwlock_wrunlock(&filter_registry.lock);
+ return error;
+}
+
+int git_filter_unregister(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef;
+ int error = 0;
+
+ assert(name);
+
+ /* cannot unregister default filters */
+ if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
+ giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name);
+ return -1;
+ }
+
+ if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock filter registry");
+ return -1;
+ }
+
+ if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
+ giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name);
+ error = GIT_ENOTFOUND;
+ goto done;
+ }
+
+ git_vector_remove(&filter_registry.filters, pos);
+
+ if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
+ fdef->filter->shutdown(fdef->filter);
+ fdef->initialized = false;
+ }
+
+ git__free(fdef->filter_name);
+ git__free(fdef->attrdata);
+ git__free(fdef);
+
+done:
+ git_rwlock_wrunlock(&filter_registry.lock);
+ return error;
+}
+
+static int filter_initialize(git_filter_def *fdef)
+{
+ int error = 0;
+
+ if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
+ if ((error = fdef->filter->initialize(fdef->filter)) < 0)
+ return error;
+ }
+
+ fdef->initialized = true;
+ return 0;
+}
+
+git_filter *git_filter_lookup(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef;
+ git_filter *filter = NULL;
+
+ if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock filter registry");
+ return NULL;
+ }
+
+ if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
+ (!fdef->initialized && filter_initialize(fdef) < 0))
+ goto done;
+
+ filter = fdef->filter;
+
+done:
+ git_rwlock_rdunlock(&filter_registry.lock);
+ return filter;
+}
+
+void git_filter_free(git_filter *filter)
+{
+ git__free(filter);
+}
+
+git_repository *git_filter_source_repo(const git_filter_source *src)
+{
+ return src->repo;
+}
+
+const char *git_filter_source_path(const git_filter_source *src)
+{
+ return src->path;
+}
+
+uint16_t git_filter_source_filemode(const git_filter_source *src)
+{
+ return src->filemode;
+}
+
+const git_oid *git_filter_source_id(const git_filter_source *src)
+{
+ return git_oid_iszero(&src->oid) ? NULL : &src->oid;
+}
+
+git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
+{
+ return src->mode;
+}
+
+uint32_t git_filter_source_flags(const git_filter_source *src)
+{
+ return src->flags;
+}
+
+static int filter_list_new(
+ git_filter_list **out, const git_filter_source *src)
+{
+ git_filter_list *fl = NULL;
+ size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+
+ fl = git__calloc(1, alloclen);
+ GITERR_CHECK_ALLOC(fl);
+
+ if (src->path)
+ memcpy(fl->path, src->path, pathlen);
+ fl->source.repo = src->repo;
+ fl->source.path = fl->path;
+ fl->source.mode = src->mode;
+ fl->source.flags = src->flags;
+
+ *out = fl;
+ return 0;
+}
+
+static int filter_list_check_attributes(
+ const char ***out,
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_filter_def *fdef,
+ const git_filter_source *src)
+{
+ int error;
+ size_t i;
+ const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
+ GITERR_CHECK_ALLOC(strs);
+
+ error = git_attr_get_many_with_session(
+ strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs);
+
+ /* if no values were found but no matches are needed, it's okay! */
+ if (error == GIT_ENOTFOUND && !fdef->nmatches) {
+ giterr_clear();
+ git__free((void *)strs);
+ return 0;
+ }
+
+ for (i = 0; !error && i < fdef->nattrs; ++i) {
+ const char *want = fdef->attrs[fdef->nattrs + i];
+ git_attr_t want_type, found_type;
+
+ if (!want)
+ continue;
+
+ want_type = git_attr_value(want);
+ found_type = git_attr_value(strs[i]);
+
+ if (want_type != found_type)
+ error = GIT_ENOTFOUND;
+ else if (want_type == GIT_ATTR_VALUE_T &&
+ strcmp(want, strs[i]) &&
+ strcmp(want, "*"))
+ error = GIT_ENOTFOUND;
+ }
+
+ if (error)
+ git__free((void *)strs);
+ else
+ *out = strs;
+
+ return error;
+}
+
+int git_filter_list_new(
+ git_filter_list **out,
+ git_repository *repo,
+ git_filter_mode_t mode,
+ uint32_t flags)
+{
+ git_filter_source src = { 0 };
+ src.repo = repo;
+ src.path = NULL;
+ src.mode = mode;
+ src.flags = flags;
+ return filter_list_new(out, &src);
+}
+
+int git_filter_list__load_ext(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ git_filter_options *filter_opts)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+ git_filter_source src = { 0 };
+ git_filter_entry *fe;
+ size_t idx;
+ git_filter_def *fdef;
+
+ if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock filter registry");
+ return -1;
+ }
+
+ src.repo = repo;
+ src.path = path;
+ src.mode = mode;
+ src.flags = filter_opts->flags;
+
+ if (blob)
+ git_oid_cpy(&src.oid, git_blob_id(blob));
+
+ git_vector_foreach(&filter_registry.filters, idx, fdef) {
+ const char **values = NULL;
+ void *payload = NULL;
+
+ if (!fdef || !fdef->filter)
+ continue;
+
+ if (fdef->nattrs > 0) {
+ error = filter_list_check_attributes(
+ &values, repo, filter_opts->attr_session, fdef, &src);
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ continue;
+ } else if (error < 0)
+ break;
+ }
+
+ if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
+ break;
+
+ if (fdef->filter->check)
+ error = fdef->filter->check(
+ fdef->filter, &payload, &src, values);
+
+ git__free((void *)values);
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ else if (error < 0)
+ break;
+ else {
+ if (!fl) {
+ if ((error = filter_list_new(&fl, &src)) < 0)
+ break;
+
+ fl->temp_buf = filter_opts->temp_buf;
+ }
+
+ fe = git_array_alloc(fl->filters);
+ GITERR_CHECK_ALLOC(fe);
+
+ fe->filter = fdef->filter;
+ fe->filter_name = fdef->filter_name;
+ fe->payload = payload;
+ }
+ }
+
+ git_rwlock_rdunlock(&filter_registry.lock);
+
+ if (error && fl != NULL) {
+ git_array_clear(fl->filters);
+ git__free(fl);
+ fl = NULL;
+ }
+
+ *filters = fl;
+ return error;
+}
+
+int git_filter_list_load(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ uint32_t flags)
+{
+ git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
+
+ filter_opts.flags = flags;
+
+ return git_filter_list__load_ext(
+ filters, repo, blob, path, mode, &filter_opts);
+}
+
+void git_filter_list_free(git_filter_list *fl)
+{
+ uint32_t i;
+
+ if (!fl)
+ return;
+
+ for (i = 0; i < git_array_size(fl->filters); ++i) {
+ git_filter_entry *fe = git_array_get(fl->filters, i);
+ if (fe->filter->cleanup)
+ fe->filter->cleanup(fe->filter, fe->payload);
+ }
+
+ git_array_clear(fl->filters);
+ git__free(fl);
+}
+
+int git_filter_list_contains(
+ git_filter_list *fl,
+ const char *name)
+{
+ size_t i;
+
+ assert(name);
+
+ if (!fl)
+ return 0;
+
+ for (i = 0; i < fl->filters.size; i++) {
+ if (strcmp(fl->filters.ptr[i].filter_name, name) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+int git_filter_list_push(
+ git_filter_list *fl, git_filter *filter, void *payload)
+{
+ int error = 0;
+ size_t pos;
+ git_filter_def *fdef = NULL;
+ git_filter_entry *fe;
+
+ assert(fl && filter);
+
+ if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock filter registry");
+ return -1;
+ }
+
+ if (git_vector_search2(
+ &pos, &filter_registry.filters,
+ filter_def_filter_key_check, filter) == 0)
+ fdef = git_vector_get(&filter_registry.filters, pos);
+
+ git_rwlock_rdunlock(&filter_registry.lock);
+
+ if (fdef == NULL) {
+ giterr_set(GITERR_FILTER, "Cannot use an unregistered filter");
+ return -1;
+ }
+
+ if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
+ return error;
+
+ fe = git_array_alloc(fl->filters);
+ GITERR_CHECK_ALLOC(fe);
+ fe->filter = filter;
+ fe->payload = payload;
+
+ return 0;
+}
+
+size_t git_filter_list_length(const git_filter_list *fl)
+{
+ return fl ? git_array_size(fl->filters) : 0;
+}
+
+struct buf_stream {
+ git_writestream parent;
+ git_buf *target;
+ bool complete;
+};
+
+static int buf_stream_write(
+ git_writestream *s, const char *buffer, size_t len)
+{
+ struct buf_stream *buf_stream = (struct buf_stream *)s;
+ assert(buf_stream);
+
+ assert(buf_stream->complete == 0);
+
+ return git_buf_put(buf_stream->target, buffer, len);
+}
+
+static int buf_stream_close(git_writestream *s)
+{
+ struct buf_stream *buf_stream = (struct buf_stream *)s;
+ assert(buf_stream);
+
+ assert(buf_stream->complete == 0);
+ buf_stream->complete = 1;
+
+ return 0;
+}
+
+static void buf_stream_free(git_writestream *s)
+{
+ GIT_UNUSED(s);
+}
+
+static void buf_stream_init(struct buf_stream *writer, git_buf *target)
+{
+ memset(writer, 0, sizeof(struct buf_stream));
+
+ writer->parent.write = buf_stream_write;
+ writer->parent.close = buf_stream_close;
+ writer->parent.free = buf_stream_free;
+ writer->target = target;
+
+ git_buf_clear(target);
+}
+
+int git_filter_list_apply_to_data(
+ git_buf *tgt, git_filter_list *filters, git_buf *src)
+{
+ struct buf_stream writer;
+ int error;
+
+ git_buf_sanitize(tgt);
+ git_buf_sanitize(src);
+
+ if (!filters) {
+ git_buf_attach_notowned(tgt, src->ptr, src->size);
+ return 0;
+ }
+
+ buf_stream_init(&writer, tgt);
+
+ if ((error = git_filter_list_stream_data(filters, src,
+ &writer.parent)) < 0)
+ return error;
+
+ assert(writer.complete);
+ return error;
+}
+
+int git_filter_list_apply_to_file(
+ git_buf *out,
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path)
+{
+ struct buf_stream writer;
+ int error;
+
+ buf_stream_init(&writer, out);
+
+ if ((error = git_filter_list_stream_file(
+ filters, repo, path, &writer.parent)) < 0)
+ return error;
+
+ assert(writer.complete);
+ return error;
+}
+
+static int buf_from_blob(git_buf *out, git_blob *blob)
+{
+ git_off_t rawsize = git_blob_rawsize(blob);
+
+ if (!git__is_sizet(rawsize)) {
+ giterr_set(GITERR_OS, "Blob is too large to filter");
+ return -1;
+ }
+
+ git_buf_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
+ return 0;
+}
+
+int git_filter_list_apply_to_blob(
+ git_buf *out,
+ git_filter_list *filters,
+ git_blob *blob)
+{
+ struct buf_stream writer;
+ int error;
+
+ buf_stream_init(&writer, out);
+
+ if ((error = git_filter_list_stream_blob(
+ filters, blob, &writer.parent)) < 0)
+ return error;
+
+ assert(writer.complete);
+ return error;
+}
+
+struct proxy_stream {
+ git_writestream parent;
+ git_filter *filter;
+ const git_filter_source *source;
+ void **payload;
+ git_buf input;
+ git_buf temp_buf;
+ git_buf *output;
+ git_writestream *target;
+};
+
+static int proxy_stream_write(
+ git_writestream *s, const char *buffer, size_t len)
+{
+ struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
+ assert(proxy_stream);
+
+ return git_buf_put(&proxy_stream->input, buffer, len);
+}
+
+static int proxy_stream_close(git_writestream *s)
+{
+ struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
+ git_buf *writebuf;
+ int error;
+
+ assert(proxy_stream);
+
+ error = proxy_stream->filter->apply(
+ proxy_stream->filter,
+ proxy_stream->payload,
+ proxy_stream->output,
+ &proxy_stream->input,
+ proxy_stream->source);
+
+ if (error == GIT_PASSTHROUGH) {
+ writebuf = &proxy_stream->input;
+ } else if (error == 0) {
+ git_buf_sanitize(proxy_stream->output);
+ writebuf = proxy_stream->output;
+ } else {
+ return error;
+ }
+
+ if ((error = proxy_stream->target->write(
+ proxy_stream->target, writebuf->ptr, writebuf->size)) == 0)
+ error = proxy_stream->target->close(proxy_stream->target);
+
+ return error;
+}
+
+static void proxy_stream_free(git_writestream *s)
+{
+ struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
+ assert(proxy_stream);
+
+ git_buf_free(&proxy_stream->input);
+ git_buf_free(&proxy_stream->temp_buf);
+ git__free(proxy_stream);
+}
+
+static int proxy_stream_init(
+ git_writestream **out,
+ git_filter *filter,
+ git_buf *temp_buf,
+ void **payload,
+ const git_filter_source *source,
+ git_writestream *target)
+{
+ struct proxy_stream *proxy_stream = git__calloc(1, sizeof(struct proxy_stream));
+ GITERR_CHECK_ALLOC(proxy_stream);
+
+ proxy_stream->parent.write = proxy_stream_write;
+ proxy_stream->parent.close = proxy_stream_close;
+ proxy_stream->parent.free = proxy_stream_free;
+ proxy_stream->filter = filter;
+ proxy_stream->payload = payload;
+ proxy_stream->source = source;
+ proxy_stream->target = target;
+ proxy_stream->output = temp_buf ? temp_buf : &proxy_stream->temp_buf;
+
+ if (temp_buf)
+ git_buf_clear(temp_buf);
+
+ *out = (git_writestream *)proxy_stream;
+ return 0;
+}
+
+static int stream_list_init(
+ git_writestream **out,
+ git_vector *streams,
+ git_filter_list *filters,
+ git_writestream *target)
+{
+ git_writestream *last_stream = target;
+ size_t i;
+ int error = 0;
+
+ *out = NULL;
+
+ if (!filters) {
+ *out = target;
+ return 0;
+ }
+
+ /* Create filters last to first to get the chaining direction */
+ for (i = 0; i < git_array_size(filters->filters); ++i) {
+ size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ?
+ git_array_size(filters->filters) - 1 - i : i;
+ git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
+ git_writestream *filter_stream;
+
+ assert(fe->filter->stream || fe->filter->apply);
+
+ /* If necessary, create a stream that proxies the traditional
+ * application.
+ */
+ if (fe->filter->stream)
+ error = fe->filter->stream(&filter_stream, fe->filter,
+ &fe->payload, &filters->source, last_stream);
+ else
+ /* Create a stream that proxies the one-shot apply */
+ error = proxy_stream_init(&filter_stream, fe->filter,
+ filters->temp_buf, &fe->payload, &filters->source,
+ last_stream);
+
+ if (error < 0)
+ return error;
+
+ git_vector_insert(streams, filter_stream);
+ last_stream = filter_stream;
+ }
+
+ *out = last_stream;
+ return 0;
+}
+
+void stream_list_free(git_vector *streams)
+{
+ git_writestream *stream;
+ size_t i;
+
+ git_vector_foreach(streams, i, stream)
+ stream->free(stream);
+ git_vector_free(streams);
+}
+
+int git_filter_list_stream_file(
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path,
+ git_writestream *target)
+{
+ char buf[FILTERIO_BUFSIZE];
+ git_buf abspath = GIT_BUF_INIT;
+ const char *base = repo ? git_repository_workdir(repo) : NULL;
+ git_vector filter_streams = GIT_VECTOR_INIT;
+ git_writestream *stream_start;
+ ssize_t readlen;
+ int fd = -1, error;
+
+ if ((error = stream_list_init(
+ &stream_start, &filter_streams, filters, target)) < 0 ||
+ (error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0)
+ goto done;
+
+ if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
+ error = fd;
+ goto done;
+ }
+
+ while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
+ if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
+ goto done;
+ }
+
+ if (!readlen)
+ error = stream_start->close(stream_start);
+ else if (readlen < 0)
+ error = readlen;
+
+
+done:
+ if (fd >= 0)
+ p_close(fd);
+ stream_list_free(&filter_streams);
+ git_buf_free(&abspath);
+ return error;
+}
+
+int git_filter_list_stream_data(
+ git_filter_list *filters,
+ git_buf *data,
+ git_writestream *target)
+{
+ git_vector filter_streams = GIT_VECTOR_INIT;
+ git_writestream *stream_start;
+ int error = 0, close_error;
+
+ git_buf_sanitize(data);
+
+ if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
+ goto out;
+
+ error = stream_start->write(stream_start, data->ptr, data->size);
+
+out:
+ close_error = stream_start->close(stream_start);
+ stream_list_free(&filter_streams);
+ /* propagate the stream init or write error */
+ return error < 0 ? error : close_error;
+}
+
+int git_filter_list_stream_blob(
+ git_filter_list *filters,
+ git_blob *blob,
+ git_writestream *target)
+{
+ git_buf in = GIT_BUF_INIT;
+
+ if (buf_from_blob(&in, blob) < 0)
+ return -1;
+
+ if (filters)
+ git_oid_cpy(&filters->source.oid, git_blob_id(blob));
+
+ return git_filter_list_stream_data(filters, &in, target);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_filter_h__
+#define INCLUDE_filter_h__
+
+#include "common.h"
+#include "attr_file.h"
+#include "git2/filter.h"
+
+/* Amount of file to examine for NUL byte when checking binary-ness */
+#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000
+
+/* Possible CRLF values */
+typedef enum {
+ GIT_CRLF_GUESS = -1,
+ GIT_CRLF_BINARY = 0,
+ GIT_CRLF_TEXT,
+ GIT_CRLF_INPUT,
+ GIT_CRLF_CRLF,
+ GIT_CRLF_AUTO,
+} git_crlf_t;
+
+typedef struct {
+ git_attr_session *attr_session;
+ git_buf *temp_buf;
+ uint32_t flags;
+} git_filter_options;
+
+#define GIT_FILTER_OPTIONS_INIT {0}
+
+extern int git_filter_global_init(void);
+
+extern void git_filter_free(git_filter *filter);
+
+extern int git_filter_list__load_ext(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ git_filter_options *filter_opts);
+
+/*
+ * Available filters
+ */
+
+extern git_filter *git_crlf_filter_new(void);
+extern git_filter *git_ident_filter_new(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+/*
+ * This file contains code originally derrived from OpenBSD fnmatch.c
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
+ * Compares a filename or pathname to a pattern.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "fnmatch.h"
+
+#define EOS '\0'
+
+#define RANGE_MATCH 1
+#define RANGE_NOMATCH 0
+#define RANGE_ERROR (-1)
+
+static int rangematch(const char *, char, int, char **);
+
+static int
+p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
+{
+ const char *stringstart;
+ char *newp;
+ char c, test;
+ int recurs_flags = flags & ~FNM_PERIOD;
+
+ if (recurs-- == 0)
+ return FNM_NORES;
+
+ for (stringstart = string;;)
+ switch (c = *pattern++) {
+ case EOS:
+ if ((flags & FNM_LEADING_DIR) && *string == '/')
+ return (0);
+ return (*string == EOS ? 0 : FNM_NOMATCH);
+ case '?':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && (flags & FNM_PATHNAME))
+ return (FNM_NOMATCH);
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ case '*':
+ c = *pattern;
+
+ /* Let '**' override PATHNAME match for this segment.
+ * It will be restored if/when we recurse below.
+ */
+ if (c == '*') {
+ c = *++pattern;
+ /* star-star-slash is at the end, match by default */
+ if (c == EOS)
+ return 0;
+ /* Double-star must be at end or between slashes */
+ if (c != '/')
+ return (FNM_NOMATCH);
+
+ c = *++pattern;
+ do {
+ int e = p_fnmatchx(pattern, string, recurs_flags, recurs);
+ if (e != FNM_NOMATCH)
+ return e;
+ string = strchr(string, '/');
+ } while (string++);
+
+ /* If we get here, we didn't find a match */
+ return FNM_NOMATCH;
+ }
+
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+
+ /* Optimize for pattern with * at end or before /. */
+ if (c == EOS) {
+ if (flags & FNM_PATHNAME)
+ return ((flags & FNM_LEADING_DIR) ||
+ strchr(string, '/') == NULL ?
+ 0 : FNM_NOMATCH);
+ else
+ return (0);
+ } else if (c == '/' && (flags & FNM_PATHNAME)) {
+ if ((string = strchr(string, '/')) == NULL)
+ return (FNM_NOMATCH);
+ break;
+ }
+
+ /* General case, use recursion. */
+ while ((test = *string) != EOS) {
+ int e;
+
+ e = p_fnmatchx(pattern, string, recurs_flags, recurs);
+ if (e != FNM_NOMATCH)
+ return e;
+ if (test == '/' && (flags & FNM_PATHNAME))
+ break;
+ ++string;
+ }
+ return (FNM_NOMATCH);
+ case '[':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && (flags & FNM_PATHNAME))
+ return (FNM_NOMATCH);
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+
+ switch (rangematch(pattern, *string, flags, &newp)) {
+ case RANGE_ERROR:
+ /* not a good range, treat as normal text */
+ goto normal;
+ case RANGE_MATCH:
+ pattern = newp;
+ break;
+ case RANGE_NOMATCH:
+ return (FNM_NOMATCH);
+ }
+ ++string;
+ break;
+ case '\\':
+ if (!(flags & FNM_NOESCAPE)) {
+ if ((c = *pattern++) == EOS) {
+ c = '\\';
+ --pattern;
+ }
+ }
+ /* FALLTHROUGH */
+ default:
+ normal:
+ if (c != *string && !((flags & FNM_CASEFOLD) &&
+ (git__tolower((unsigned char)c) ==
+ git__tolower((unsigned char)*string))))
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ }
+ /* NOTREACHED */
+}
+
+static int
+rangematch(const char *pattern, char test, int flags, char **newp)
+{
+ int negate, ok;
+ char c, c2;
+
+ /*
+ * A bracket expression starting with an unquoted circumflex
+ * character produces unspecified results (IEEE 1003.2-1992,
+ * 3.13.2). This implementation treats it like '!', for
+ * consistency with the regular expression syntax.
+ * J.T. Conklin (conklin@ngai.kaleida.com)
+ */
+ if ((negate = (*pattern == '!' || *pattern == '^')) != 0)
+ ++pattern;
+
+ if (flags & FNM_CASEFOLD)
+ test = (char)git__tolower((unsigned char)test);
+
+ /*
+ * A right bracket shall lose its special meaning and represent
+ * itself in a bracket expression if it occurs first in the list.
+ * -- POSIX.2 2.8.3.2
+ */
+ ok = 0;
+ c = *pattern++;
+ do {
+ if (c == '\\' && !(flags & FNM_NOESCAPE))
+ c = *pattern++;
+ if (c == EOS)
+ return (RANGE_ERROR);
+ if (c == '/' && (flags & FNM_PATHNAME))
+ return (RANGE_NOMATCH);
+ if ((flags & FNM_CASEFOLD))
+ c = (char)git__tolower((unsigned char)c);
+ if (*pattern == '-'
+ && (c2 = *(pattern+1)) != EOS && c2 != ']') {
+ pattern += 2;
+ if (c2 == '\\' && !(flags & FNM_NOESCAPE))
+ c2 = *pattern++;
+ if (c2 == EOS)
+ return (RANGE_ERROR);
+ if (flags & FNM_CASEFOLD)
+ c2 = (char)git__tolower((unsigned char)c2);
+ if (c <= test && test <= c2)
+ ok = 1;
+ } else if (c == test)
+ ok = 1;
+ } while ((c = *pattern++) != ']');
+
+ *newp = (char *)pattern;
+ return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
+}
+
+int
+p_fnmatch(const char *pattern, const char *string, int flags)
+{
+ return p_fnmatchx(pattern, string, flags, 64);
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef INCLUDE_fnmatch__compat_h__
+#define INCLUDE_fnmatch__compat_h__
+
+#include "common.h"
+
+#define FNM_NOMATCH 1 /* Match failed. */
+#define FNM_NOSYS 2 /* Function not supported (unused). */
+#define FNM_NORES 3 /* Out of resources */
+
+#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
+#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
+#define FNM_PERIOD 0x04 /* Period must be matched by period. */
+#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
+#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
+
+#define FNM_IGNORECASE FNM_CASEFOLD
+#define FNM_FILE_NAME FNM_PATHNAME
+
+extern int p_fnmatch(const char *pattern, const char *string, int flags);
+
+#endif /* _FNMATCH_H */
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "global.h"
+#include "hash.h"
+#include "sysdir.h"
+#include "filter.h"
+#include "merge_driver.h"
+#include "openssl_stream.h"
+#include "thread-utils.h"
+#include "git2/global.h"
+#include "transports/ssh.h"
+
+#if defined(GIT_MSVC_CRTDBG)
+#include "win32/w32_stack.h"
+#include "win32/w32_crtdbg_stacktrace.h"
+#endif
+
+git_mutex git__mwindow_mutex;
+
+#define MAX_SHUTDOWN_CB 8
+
+static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
+static git_atomic git__n_shutdown_callbacks;
+static git_atomic git__n_inits;
+char *git__user_agent;
+char *git__ssl_ciphers;
+
+void git__on_shutdown(git_global_shutdown_fn callback)
+{
+ int count = git_atomic_inc(&git__n_shutdown_callbacks);
+ assert(count <= MAX_SHUTDOWN_CB && count > 0);
+ git__shutdown_callbacks[count - 1] = callback;
+}
+
+static void git__global_state_cleanup(git_global_st *st)
+{
+ if (!st)
+ return;
+
+ git__free(st->error_t.message);
+ st->error_t.message = NULL;
+}
+
+static int init_common(void)
+{
+ int ret;
+
+ /* Initialize the CRT debug allocator first, before our first malloc */
+#if defined(GIT_MSVC_CRTDBG)
+ git_win32__crtdbg_stacktrace_init();
+ git_win32__stack_init();
+#endif
+
+ /* Initialize any other subsystems that have global state */
+ if ((ret = git_hash_global_init()) == 0 &&
+ (ret = git_sysdir_global_init()) == 0 &&
+ (ret = git_filter_global_init()) == 0 &&
+ (ret = git_merge_driver_global_init()) == 0 &&
+ (ret = git_transport_ssh_global_init()) == 0 &&
+ (ret = git_openssl_stream_global_init()) == 0)
+ ret = git_mwindow_global_init();
+
+ GIT_MEMORY_BARRIER;
+
+ return ret;
+}
+
+static void shutdown_common(void)
+{
+ int pos;
+
+ /* Shutdown subsystems that have registered */
+ for (pos = git_atomic_get(&git__n_shutdown_callbacks);
+ pos > 0;
+ pos = git_atomic_dec(&git__n_shutdown_callbacks)) {
+
+ git_global_shutdown_fn cb = git__swap(
+ git__shutdown_callbacks[pos - 1], NULL);
+
+ if (cb != NULL)
+ cb();
+ }
+
+ git__free(git__user_agent);
+ git__free(git__ssl_ciphers);
+}
+
+/**
+ * Handle the global state with TLS
+ *
+ * If libgit2 is built with GIT_THREADS enabled,
+ * the `git_libgit2_init()` function must be called
+ * before calling any other function of the library.
+ *
+ * This function allocates a TLS index (using pthreads
+ * or the native Win32 API) to store the global state
+ * on a per-thread basis.
+ *
+ * Any internal method that requires global state will
+ * then call `git__global_state()` which returns a pointer
+ * to the global state structure; this pointer is lazily
+ * allocated on each thread.
+ *
+ * Before shutting down the library, the
+ * `git_libgit2_shutdown` method must be called to free
+ * the previously reserved TLS index.
+ *
+ * If libgit2 is built without threading support, the
+ * `git__global_statestate()` call returns a pointer to a single,
+ * statically allocated global state. The `git_thread_`
+ * functions are not available in that case.
+ */
+
+/*
+ * `git_libgit2_init()` allows subsystems to perform global setup,
+ * which may take place in the global scope. An explicit memory
+ * fence exists at the exit of `git_libgit2_init()`. Without this,
+ * CPU cores are free to reorder cache invalidation of `_tls_init`
+ * before cache invalidation of the subsystems' newly written global
+ * state.
+ */
+#if defined(GIT_THREADS) && defined(GIT_WIN32)
+
+static DWORD _tls_index;
+static volatile LONG _mutex = 0;
+
+static int synchronized_threads_init(void)
+{
+ int error;
+
+ _tls_index = TlsAlloc();
+
+ git_threads_init();
+
+ if (git_mutex_init(&git__mwindow_mutex))
+ return -1;
+
+ error = init_common();
+
+ return error;
+}
+
+int git_libgit2_init(void)
+{
+ int ret;
+
+ /* Enter the lock */
+ while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
+
+ /* Only do work on a 0 -> 1 transition of the refcount */
+ if ((ret = git_atomic_inc(&git__n_inits)) == 1) {
+ if (synchronized_threads_init() < 0)
+ ret = -1;
+ }
+
+ /* Exit the lock */
+ InterlockedExchange(&_mutex, 0);
+
+ return ret;
+}
+
+int git_libgit2_shutdown(void)
+{
+ int ret;
+
+ /* Enter the lock */
+ while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
+
+ /* Only do work on a 1 -> 0 transition of the refcount */
+ if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
+ shutdown_common();
+
+ git__free_tls_data();
+
+ TlsFree(_tls_index);
+ git_mutex_free(&git__mwindow_mutex);
+
+#if defined(GIT_MSVC_CRTDBG)
+ git_win32__crtdbg_stacktrace_cleanup();
+ git_win32__stack_cleanup();
+#endif
+ }
+
+ /* Exit the lock */
+ InterlockedExchange(&_mutex, 0);
+
+ return ret;
+}
+
+git_global_st *git__global_state(void)
+{
+ git_global_st *ptr;
+
+ assert(git_atomic_get(&git__n_inits) > 0);
+
+ if ((ptr = TlsGetValue(_tls_index)) != NULL)
+ return ptr;
+
+ ptr = git__calloc(1, sizeof(git_global_st));
+ if (!ptr)
+ return NULL;
+
+ git_buf_init(&ptr->error_buf, 0);
+
+ TlsSetValue(_tls_index, ptr);
+ return ptr;
+}
+
+/**
+ * Free the TLS data associated with this thread.
+ * This should only be used by the thread as it
+ * is exiting.
+ */
+void git__free_tls_data(void)
+{
+ void *ptr = TlsGetValue(_tls_index);
+ if (!ptr)
+ return;
+
+ git__global_state_cleanup(ptr);
+ git__free(ptr);
+ TlsSetValue(_tls_index, NULL);
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
+{
+ GIT_UNUSED(hInstDll);
+ GIT_UNUSED(lpvReserved);
+
+ /* This is how Windows lets us know our thread is being shut down */
+ if (fdwReason == DLL_THREAD_DETACH) {
+ git__free_tls_data();
+ }
+
+ /*
+ * Windows pays attention to this during library loading. We don't do anything
+ * so we trivially succeed.
+ */
+ return TRUE;
+}
+
+#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
+
+static pthread_key_t _tls_key;
+static pthread_mutex_t _init_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_once_t _once_init = PTHREAD_ONCE_INIT;
+int init_error = 0;
+
+static void cb__free_status(void *st)
+{
+ git__global_state_cleanup(st);
+ git__free(st);
+}
+
+static void init_once(void)
+{
+ if ((init_error = git_mutex_init(&git__mwindow_mutex)) != 0)
+ return;
+
+ pthread_key_create(&_tls_key, &cb__free_status);
+
+ init_error = init_common();
+}
+
+int git_libgit2_init(void)
+{
+ int ret, err;
+
+ ret = git_atomic_inc(&git__n_inits);
+
+ if ((err = pthread_mutex_lock(&_init_mutex)) != 0)
+ return err;
+ err = pthread_once(&_once_init, init_once);
+ err |= pthread_mutex_unlock(&_init_mutex);
+
+ if (err || init_error)
+ return err | init_error;
+
+ return ret;
+}
+
+int git_libgit2_shutdown(void)
+{
+ void *ptr = NULL;
+ pthread_once_t new_once = PTHREAD_ONCE_INIT;
+ int ret;
+
+ if ((ret = git_atomic_dec(&git__n_inits)) != 0)
+ return ret;
+
+ if ((ret = pthread_mutex_lock(&_init_mutex)) != 0)
+ return ret;
+
+ /* Shut down any subsystems that have global state */
+ shutdown_common();
+
+ ptr = pthread_getspecific(_tls_key);
+ pthread_setspecific(_tls_key, NULL);
+
+ git__global_state_cleanup(ptr);
+ git__free(ptr);
+
+ pthread_key_delete(_tls_key);
+ git_mutex_free(&git__mwindow_mutex);
+ _once_init = new_once;
+
+ if ((ret = pthread_mutex_unlock(&_init_mutex)) != 0)
+ return ret;
+
+ return 0;
+}
+
+git_global_st *git__global_state(void)
+{
+ git_global_st *ptr;
+
+ assert(git_atomic_get(&git__n_inits) > 0);
+
+ if ((ptr = pthread_getspecific(_tls_key)) != NULL)
+ return ptr;
+
+ ptr = git__calloc(1, sizeof(git_global_st));
+ if (!ptr)
+ return NULL;
+
+ git_buf_init(&ptr->error_buf, 0);
+ pthread_setspecific(_tls_key, ptr);
+ return ptr;
+}
+
+#else
+
+static git_global_st __state;
+
+int git_libgit2_init(void)
+{
+ int ret;
+
+ /* Only init subsystems the first time */
+ if ((ret = git_atomic_inc(&git__n_inits)) != 1)
+ return ret;
+
+ if ((ret = init_common()) < 0)
+ return ret;
+
+ return 1;
+}
+
+int git_libgit2_shutdown(void)
+{
+ int ret;
+
+ /* Shut down any subsystems that have global state */
+ if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
+ shutdown_common();
+ git__global_state_cleanup(&__state);
+ memset(&__state, 0, sizeof(__state));
+ }
+
+ return ret;
+}
+
+git_global_st *git__global_state(void)
+{
+ return &__state;
+}
+
+#endif /* GIT_THREADS */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_global_h__
+#define INCLUDE_global_h__
+
+#include "common.h"
+#include "mwindow.h"
+#include "hash.h"
+
+typedef struct {
+ git_error *last_error;
+ git_error error_t;
+ git_buf error_buf;
+ char oid_fmt[GIT_OID_HEXSZ+1];
+
+ /* On Windows, this is the current child thread that was started by
+ * `git_thread_create`. This is used to set the thread's exit code
+ * when terminated by `git_thread_exit`. It is unused on POSIX.
+ */
+ git_thread *current_thread;
+} git_global_st;
+
+#ifdef GIT_OPENSSL
+# include <openssl/ssl.h>
+extern SSL_CTX *git__ssl_ctx;
+#endif
+
+git_global_st *git__global_state(void);
+
+extern git_mutex git__mwindow_mutex;
+
+#define GIT_GLOBAL (git__global_state())
+
+typedef void (*git_global_shutdown_fn)(void);
+
+extern void git__on_shutdown(git_global_shutdown_fn callback);
+
+extern void git__free_tls_data(void);
+
+extern const char *git_libgit2__user_agent(void);
+extern const char *git_libgit2__ssl_ciphers(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "revwalk.h"
+#include "merge.h"
+#include "git2/graph.h"
+
+static int interesting(git_pqueue *list, git_commit_list *roots)
+{
+ unsigned int i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ while(roots) {
+ if ((roots->item->flags & STALE) == 0)
+ return 1;
+ roots = roots->next;
+ }
+
+ return 0;
+}
+
+static int mark_parents(git_revwalk *walk, git_commit_list_node *one,
+ git_commit_list_node *two)
+{
+ unsigned int i;
+ git_commit_list *roots = NULL;
+ git_pqueue list;
+
+ /* if the commit is repeated, we have a our merge base already */
+ if (one == two) {
+ one->flags |= PARENT1 | PARENT2 | RESULT;
+ return 0;
+ }
+
+ if (git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ goto on_error;
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ goto on_error;
+
+ if (git_commit_list_parse(walk, two) < 0)
+ goto on_error;
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ goto on_error;
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list, roots)) {
+ git_commit_list_node *commit = git_pqueue_pop(&list);
+ unsigned int flags;
+
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT))
+ commit->flags |= RESULT;
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if (git_commit_list_parse(walk, p) < 0)
+ goto on_error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ goto on_error;
+ }
+
+ /* Keep track of root commits, to make sure the path gets marked */
+ if (commit->out_degree == 0) {
+ if (git_commit_list_insert(commit, &roots) == NULL)
+ goto on_error;
+ }
+ }
+
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return 0;
+
+on_error:
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return -1;
+}
+
+
+static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
+ size_t *ahead, size_t *behind)
+{
+ git_commit_list_node *commit;
+ git_pqueue pq;
+ int error = 0, i;
+ *ahead = 0;
+ *behind = 0;
+
+ if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if ((error = git_pqueue_insert(&pq, one)) < 0 ||
+ (error = git_pqueue_insert(&pq, two)) < 0)
+ goto done;
+
+ while ((commit = git_pqueue_pop(&pq)) != NULL) {
+ if (commit->flags & RESULT ||
+ (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))
+ continue;
+ else if (commit->flags & PARENT1)
+ (*ahead)++;
+ else if (commit->flags & PARENT2)
+ (*behind)++;
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((error = git_pqueue_insert(&pq, p)) < 0)
+ goto done;
+ }
+ commit->flags |= RESULT;
+ }
+
+done:
+ git_pqueue_free(&pq);
+ return error;
+}
+
+int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
+ const git_oid *local, const git_oid *upstream)
+{
+ git_revwalk *walk;
+ git_commit_list_node *commit_u, *commit_l;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit_u = git_revwalk__commit_lookup(walk, upstream);
+ if (commit_u == NULL)
+ goto on_error;
+
+ commit_l = git_revwalk__commit_lookup(walk, local);
+ if (commit_l == NULL)
+ goto on_error;
+
+ if (mark_parents(walk, commit_l, commit_u) < 0)
+ goto on_error;
+ if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)
+ goto on_error;
+
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+}
+
+int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor)
+{
+ git_oid merge_base;
+ int error;
+
+ if (git_oid_equal(commit, ancestor))
+ return 0;
+
+ error = git_merge_base(&merge_base, repo, commit, ancestor);
+ /* No merge-base found, it's not a descendant */
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ return git_oid_equal(&merge_base, ancestor);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "hash.h"
+
+int git_hash_buf(git_oid *out, const void *data, size_t len)
+{
+ git_hash_ctx ctx;
+ int error = 0;
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
+
+ if ((error = git_hash_update(&ctx, data, len)) >= 0)
+ error = git_hash_final(out, &ctx);
+
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
+}
+
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
+{
+ git_hash_ctx ctx;
+ size_t i;
+ int error = 0;
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)
+ goto done;
+ }
+
+ error = git_hash_final(out, &ctx);
+
+done:
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_hash_h__
+#define INCLUDE_hash_h__
+
+#include "git2/oid.h"
+
+typedef struct git_hash_prov git_hash_prov;
+typedef struct git_hash_ctx git_hash_ctx;
+
+int git_hash_global_init(void);
+int git_hash_ctx_init(git_hash_ctx *ctx);
+void git_hash_ctx_cleanup(git_hash_ctx *ctx);
+
+#if defined(GIT_COMMON_CRYPTO)
+# include "hash/hash_common_crypto.h"
+#elif defined(OPENSSL_SHA1)
+# include "hash/hash_openssl.h"
+#elif defined(WIN32_SHA1)
+# include "hash/hash_win32.h"
+#else
+# include "hash/hash_generic.h"
+#endif
+
+typedef struct {
+ void *data;
+ size_t len;
+} git_buf_vec;
+
+int git_hash_init(git_hash_ctx *c);
+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
+int git_hash_final(git_oid *out, git_hash_ctx *c);
+
+int git_hash_buf(git_oid *out, const void *data, size_t len);
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
+
+#endif /* INCLUDE_hash_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_hash_common_crypto_h__
+#define INCLUDE_hash_common_crypto_h__
+
+#include "hash.h"
+
+#include <CommonCrypto/CommonDigest.h>
+
+struct git_hash_ctx {
+ CC_SHA1_CTX c;
+};
+
+#define git_hash_global_init() 0
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx);
+ CC_SHA1_Init(&ctx->c);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx);
+ CC_SHA1_Update(&ctx->c, data, len);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx);
+ CC_SHA1_Final(out->id, &ctx->c);
+ return 0;
+}
+
+#endif /* INCLUDE_hash_common_crypto_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "hash.h"
+#include "hash/hash_generic.h"
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+/*
+ * Force usage of rol or ror by selecting the one with the smaller constant.
+ * It _can_ generate slightly smaller code (a constant of 1 is special), but
+ * perhaps more importantly it's possibly faster on any uarch that does a
+ * rotate with a loop.
+ */
+
+#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }))
+#define SHA_ROL(x,n) SHA_ASM("rol", x, n)
+#define SHA_ROR(x,n) SHA_ASM("ror", x, n)
+
+#else
+
+#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r)))
+#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n))
+#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n)
+
+#endif
+
+/*
+ * If you have 32 registers or more, the compiler can (and should)
+ * try to change the array[] accesses into registers. However, on
+ * machines with less than ~25 registers, that won't really work,
+ * and at least gcc will make an unholy mess of it.
+ *
+ * So to avoid that mess which just slows things down, we force
+ * the stores to memory to actually happen (we might be better off
+ * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as
+ * suggested by Artur Skawina - that will also make gcc unable to
+ * try to do the silly "optimize away loads" part because it won't
+ * see what the value will be).
+ *
+ * Ben Herrenschmidt reports that on PPC, the C version comes close
+ * to the optimized asm with this (ie on PPC you don't want that
+ * 'volatile', since there are lots of registers).
+ *
+ * On ARM we get the best code generation by forcing a full memory barrier
+ * between each SHA_ROUND, otherwise gcc happily get wild with spilling and
+ * the stack frame size simply explode and performance goes down the drain.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+ #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))
+#elif defined(__GNUC__) && defined(__arm__)
+ #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0)
+#else
+ #define setW(x, val) (W(x) = (val))
+#endif
+
+/*
+ * Performance might be improved if the CPU architecture is OK with
+ * unaligned 32-bit loads and a fast ntohl() is available.
+ * Otherwise fall back to byte loads and shifts which is portable,
+ * and is faster on architectures with memory alignment issues.
+ */
+
+#if defined(__i386__) || defined(__x86_64__) || \
+ defined(_M_IX86) || defined(_M_X64) || \
+ defined(__ppc__) || defined(__ppc64__) || \
+ defined(__powerpc__) || defined(__powerpc64__) || \
+ defined(__s390__) || defined(__s390x__)
+
+#define get_be32(p) ntohl(*(const unsigned int *)(p))
+#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
+
+#else
+
+#define get_be32(p) ( \
+ (*((const unsigned char *)(p) + 0) << 24) | \
+ (*((const unsigned char *)(p) + 1) << 16) | \
+ (*((const unsigned char *)(p) + 2) << 8) | \
+ (*((const unsigned char *)(p) + 3) << 0) )
+#define put_be32(p, v) do { \
+ unsigned int __v = (v); \
+ *((unsigned char *)(p) + 0) = __v >> 24; \
+ *((unsigned char *)(p) + 1) = __v >> 16; \
+ *((unsigned char *)(p) + 2) = __v >> 8; \
+ *((unsigned char *)(p) + 3) = __v >> 0; } while (0)
+
+#endif
+
+/* This "rolls" over the 512-bit array */
+#define W(x) (array[(x)&15])
+
+/*
+ * Where do we get the source from? The first 16 iterations get it from
+ * the input data, the next mix it from the 512-bit array.
+ */
+#define SHA_SRC(t) get_be32(data + t)
+#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)
+
+#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
+ unsigned int TEMP = input(t); setW(t, TEMP); \
+ E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \
+ B = SHA_ROR(B, 2); } while (0)
+
+#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
+#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
+#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E )
+#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
+#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )
+
+static void hash__block(git_hash_ctx *ctx, const unsigned int *data)
+{
+ unsigned int A,B,C,D,E;
+ unsigned int array[16];
+
+ A = ctx->H[0];
+ B = ctx->H[1];
+ C = ctx->H[2];
+ D = ctx->H[3];
+ E = ctx->H[4];
+
+ /* Round 1 - iterations 0-16 take their input from 'data' */
+ T_0_15( 0, A, B, C, D, E);
+ T_0_15( 1, E, A, B, C, D);
+ T_0_15( 2, D, E, A, B, C);
+ T_0_15( 3, C, D, E, A, B);
+ T_0_15( 4, B, C, D, E, A);
+ T_0_15( 5, A, B, C, D, E);
+ T_0_15( 6, E, A, B, C, D);
+ T_0_15( 7, D, E, A, B, C);
+ T_0_15( 8, C, D, E, A, B);
+ T_0_15( 9, B, C, D, E, A);
+ T_0_15(10, A, B, C, D, E);
+ T_0_15(11, E, A, B, C, D);
+ T_0_15(12, D, E, A, B, C);
+ T_0_15(13, C, D, E, A, B);
+ T_0_15(14, B, C, D, E, A);
+ T_0_15(15, A, B, C, D, E);
+
+ /* Round 1 - tail. Input from 512-bit mixing array */
+ T_16_19(16, E, A, B, C, D);
+ T_16_19(17, D, E, A, B, C);
+ T_16_19(18, C, D, E, A, B);
+ T_16_19(19, B, C, D, E, A);
+
+ /* Round 2 */
+ T_20_39(20, A, B, C, D, E);
+ T_20_39(21, E, A, B, C, D);
+ T_20_39(22, D, E, A, B, C);
+ T_20_39(23, C, D, E, A, B);
+ T_20_39(24, B, C, D, E, A);
+ T_20_39(25, A, B, C, D, E);
+ T_20_39(26, E, A, B, C, D);
+ T_20_39(27, D, E, A, B, C);
+ T_20_39(28, C, D, E, A, B);
+ T_20_39(29, B, C, D, E, A);
+ T_20_39(30, A, B, C, D, E);
+ T_20_39(31, E, A, B, C, D);
+ T_20_39(32, D, E, A, B, C);
+ T_20_39(33, C, D, E, A, B);
+ T_20_39(34, B, C, D, E, A);
+ T_20_39(35, A, B, C, D, E);
+ T_20_39(36, E, A, B, C, D);
+ T_20_39(37, D, E, A, B, C);
+ T_20_39(38, C, D, E, A, B);
+ T_20_39(39, B, C, D, E, A);
+
+ /* Round 3 */
+ T_40_59(40, A, B, C, D, E);
+ T_40_59(41, E, A, B, C, D);
+ T_40_59(42, D, E, A, B, C);
+ T_40_59(43, C, D, E, A, B);
+ T_40_59(44, B, C, D, E, A);
+ T_40_59(45, A, B, C, D, E);
+ T_40_59(46, E, A, B, C, D);
+ T_40_59(47, D, E, A, B, C);
+ T_40_59(48, C, D, E, A, B);
+ T_40_59(49, B, C, D, E, A);
+ T_40_59(50, A, B, C, D, E);
+ T_40_59(51, E, A, B, C, D);
+ T_40_59(52, D, E, A, B, C);
+ T_40_59(53, C, D, E, A, B);
+ T_40_59(54, B, C, D, E, A);
+ T_40_59(55, A, B, C, D, E);
+ T_40_59(56, E, A, B, C, D);
+ T_40_59(57, D, E, A, B, C);
+ T_40_59(58, C, D, E, A, B);
+ T_40_59(59, B, C, D, E, A);
+
+ /* Round 4 */
+ T_60_79(60, A, B, C, D, E);
+ T_60_79(61, E, A, B, C, D);
+ T_60_79(62, D, E, A, B, C);
+ T_60_79(63, C, D, E, A, B);
+ T_60_79(64, B, C, D, E, A);
+ T_60_79(65, A, B, C, D, E);
+ T_60_79(66, E, A, B, C, D);
+ T_60_79(67, D, E, A, B, C);
+ T_60_79(68, C, D, E, A, B);
+ T_60_79(69, B, C, D, E, A);
+ T_60_79(70, A, B, C, D, E);
+ T_60_79(71, E, A, B, C, D);
+ T_60_79(72, D, E, A, B, C);
+ T_60_79(73, C, D, E, A, B);
+ T_60_79(74, B, C, D, E, A);
+ T_60_79(75, A, B, C, D, E);
+ T_60_79(76, E, A, B, C, D);
+ T_60_79(77, D, E, A, B, C);
+ T_60_79(78, C, D, E, A, B);
+ T_60_79(79, B, C, D, E, A);
+
+ ctx->H[0] += A;
+ ctx->H[1] += B;
+ ctx->H[2] += C;
+ ctx->H[3] += D;
+ ctx->H[4] += E;
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ ctx->size = 0;
+
+ /* Initialize H with the magic constants (see FIPS180 for constants) */
+ ctx->H[0] = 0x67452301;
+ ctx->H[1] = 0xefcdab89;
+ ctx->H[2] = 0x98badcfe;
+ ctx->H[3] = 0x10325476;
+ ctx->H[4] = 0xc3d2e1f0;
+
+ return 0;
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ unsigned int lenW = ctx->size & 63;
+
+ ctx->size += len;
+
+ /* Read the data into W and process blocks as they get full */
+ if (lenW) {
+ unsigned int left = 64 - lenW;
+ if (len < left)
+ left = (unsigned int)len;
+ memcpy(lenW + (char *)ctx->W, data, left);
+ lenW = (lenW + left) & 63;
+ len -= left;
+ data = ((const char *)data + left);
+ if (lenW)
+ return 0;
+ hash__block(ctx, ctx->W);
+ }
+ while (len >= 64) {
+ hash__block(ctx, data);
+ data = ((const char *)data + 64);
+ len -= 64;
+ }
+ if (len)
+ memcpy(ctx->W, data, len);
+
+ return 0;
+}
+
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ static const unsigned char pad[64] = { 0x80 };
+ unsigned int padlen[2];
+ int i;
+
+ /* Pad with a binary 1 (ie 0x80), then zeroes, then length */
+ padlen[0] = htonl((uint32_t)(ctx->size >> 29));
+ padlen[1] = htonl((uint32_t)(ctx->size << 3));
+
+ i = ctx->size & 63;
+ git_hash_update(ctx, pad, 1+ (63 & (55 - i)));
+ git_hash_update(ctx, padlen, 8);
+
+ /* Output hash */
+ for (i = 0; i < 5; i++)
+ put_be32(out->id + i*4, ctx->H[i]);
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_hash_generic_h__
+#define INCLUDE_hash_generic_h__
+
+#include "hash.h"
+
+struct git_hash_ctx {
+ unsigned long long size;
+ unsigned int H[5];
+ unsigned int W[16];
+};
+
+#define git_hash_global_init() 0
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+#endif /* INCLUDE_hash_generic_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_hash_openssl_h__
+#define INCLUDE_hash_openssl_h__
+
+#include "hash.h"
+
+#include <openssl/sha.h>
+
+struct git_hash_ctx {
+ SHA_CTX c;
+};
+
+#define git_hash_global_init() 0
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Init(&ctx->c);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx);
+ SHA1_Update(&ctx->c, data, len);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Final(out->id, &ctx->c);
+ return 0;
+}
+
+#endif /* INCLUDE_hash_openssl_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "global.h"
+#include "hash.h"
+#include "hash/hash_win32.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+static struct git_hash_prov hash_prov = {0};
+
+/* Hash initialization */
+
+/* Initialize CNG, if available */
+GIT_INLINE(int) hash_cng_prov_init(void)
+{
+ char dll_path[MAX_PATH];
+ DWORD dll_path_len, size_len;
+
+ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
+ if (!git_has_win32_version(6, 0, 1))
+ return -1;
+
+ /* Load bcrypt.dll explicitly from the system directory */
+ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
+ dll_path_len > MAX_PATH ||
+ StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
+ StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
+ (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL)
+ return -1;
+
+ /* Load the function addresses */
+ if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL ||
+ (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL ||
+ (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL ||
+ (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL ||
+ (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL ||
+ (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL ||
+ (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Load the SHA1 algorithm */
+ if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Get storage space for the hash object */
+ if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) {
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ hash_prov.type = CNG;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cng_prov_shutdown(void)
+{
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+
+ hash_prov.type = INVALID;
+}
+
+/* Initialize CryptoAPI */
+GIT_INLINE(int) hash_cryptoapi_prov_init()
+{
+ if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
+ return -1;
+
+ hash_prov.type = CRYPTOAPI;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
+{
+ CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);
+
+ hash_prov.type = INVALID;
+}
+
+static void git_hash_global_shutdown(void)
+{
+ if (hash_prov.type == CNG)
+ hash_cng_prov_shutdown();
+ else if(hash_prov.type == CRYPTOAPI)
+ hash_cryptoapi_prov_shutdown();
+}
+
+int git_hash_global_init(void)
+{
+ int error = 0;
+
+ if (hash_prov.type != INVALID)
+ return 0;
+
+ if ((error = hash_cng_prov_init()) < 0)
+ error = hash_cryptoapi_prov_init();
+
+ git__on_shutdown(git_hash_global_shutdown);
+
+ return error;
+}
+
+/* CryptoAPI: available in Windows XP and newer */
+
+GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx)
+{
+ ctx->type = CRYPTOAPI;
+ ctx->prov = &hash_prov;
+
+ return git_hash_init(ctx);
+}
+
+GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+
+ if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
+ ctx->ctx.cryptoapi.valid = 0;
+ return -1;
+ }
+
+ ctx->ctx.cryptoapi.valid = 1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0))
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx)
+{
+ DWORD len = 20;
+ int error = 0;
+
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0))
+ error = -1;
+
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+ ctx->ctx.cryptoapi.valid = 0;
+
+ return error;
+}
+
+GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+}
+
+/* CNG: Available in Windows Server 2008 and newer */
+
+GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
+ return -1;
+
+ if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+ return -1;
+ }
+
+ ctx->type = CNG;
+ ctx->prov = &hash_prov;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx)
+{
+ BYTE hash[GIT_OID_RAWSZ];
+
+ if (!ctx->ctx.cng.updated)
+ return 0;
+
+ /* CNG needs to be finished to restart */
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx)
+{
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx)
+{
+ ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
+ git__free(ctx->ctx.cng.hash_object);
+}
+
+/* Indirection between CryptoAPI and CNG */
+
+int git_hash_ctx_init(git_hash_ctx *ctx)
+{
+ int error = 0;
+
+ assert(ctx);
+
+ /*
+ * When compiled with GIT_THREADS, the global hash_prov data is
+ * initialized with git_libgit2_init. Otherwise, it must be initialized
+ * at first use.
+ */
+ if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0)
+ return error;
+
+ memset(ctx, 0x0, sizeof(git_hash_ctx));
+
+ return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
+}
+
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
+}
+
+void git_hash_ctx_cleanup(git_hash_ctx *ctx)
+{
+ assert(ctx);
+
+ if (ctx->type == CNG)
+ hash_ctx_cng_cleanup(ctx);
+ else if(ctx->type == CRYPTOAPI)
+ hash_ctx_cryptoapi_cleanup(ctx);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_hash_win32_h__
+#define INCLUDE_hash_win32_h__
+
+#include "common.h"
+#include "hash.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+enum hash_win32_prov_type {
+ INVALID = 0,
+ CRYPTOAPI,
+ CNG
+};
+
+/*
+ * CryptoAPI is available for hashing on Windows XP and newer.
+ */
+
+struct hash_cryptoapi_prov {
+ HCRYPTPROV handle;
+};
+
+/*
+ * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
+ * preferred, however it is only available on Windows 2008 and newer and
+ * must therefore be dynamically loaded, and we must inline constants that
+ * would not exist when building in pre-Windows 2008 environments.
+ */
+
+#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll"
+
+/* BCRYPT_SHA1_ALGORITHM */
+#define GIT_HASH_CNG_HASH_TYPE L"SHA1"
+
+/* BCRYPT_OBJECT_LENGTH */
+#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
+
+/* BCRYPT_HASH_REUSEABLE_FLAGS */
+#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
+
+/* Function declarations for CNG */
+typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ DWORD dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)(
+ HANDLE /* BCRYPT_HANDLE */ hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG *pcbResult,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject,
+ PUCHAR pbSecret,
+ ULONG cbSecret,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ ULONG dwFlags);
+
+struct hash_cng_prov {
+ /* DLL for CNG */
+ HINSTANCE dll;
+
+ /* Function pointers for CNG */
+ hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider;
+ hash_win32_cng_get_property_fn get_property;
+ hash_win32_cng_create_hash_fn create_hash;
+ hash_win32_cng_finish_hash_fn finish_hash;
+ hash_win32_cng_hash_data_fn hash_data;
+ hash_win32_cng_destroy_hash_fn destroy_hash;
+ hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider;
+
+ HANDLE /* BCRYPT_ALG_HANDLE */ handle;
+ DWORD hash_object_size;
+};
+
+struct git_hash_prov {
+ enum hash_win32_prov_type type;
+
+ union {
+ struct hash_cryptoapi_prov cryptoapi;
+ struct hash_cng_prov cng;
+ } prov;
+};
+
+/* Hash contexts */
+
+struct hash_cryptoapi_ctx {
+ bool valid;
+ HCRYPTHASH hash_handle;
+};
+
+struct hash_cng_ctx {
+ bool updated;
+ HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle;
+ PBYTE hash_object;
+};
+
+struct git_hash_ctx {
+ enum hash_win32_prov_type type;
+ git_hash_prov *prov;
+
+ union {
+ struct hash_cryptoapi_ctx cryptoapi;
+ struct hash_cng_ctx cng;
+ } ctx;
+};
+
+#endif /* INCLUDE_hash_openssl_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/sys/hashsig.h"
+#include "fileops.h"
+#include "util.h"
+
+typedef uint32_t hashsig_t;
+typedef uint64_t hashsig_state;
+
+#define HASHSIG_SCALE 100
+
+#define HASHSIG_MAX_RUN 80
+#define HASHSIG_HASH_START 0x012345678ABCDEF0LL
+#define HASHSIG_HASH_SHIFT 5
+
+#define HASHSIG_HASH_MIX(S,CH) \
+ (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH)
+
+#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
+#define HASHSIG_HEAP_MIN_SIZE 4
+
+typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
+
+typedef struct {
+ int size, asize;
+ hashsig_cmp cmp;
+ hashsig_t values[HASHSIG_HEAP_SIZE];
+} hashsig_heap;
+
+struct git_hashsig {
+ hashsig_heap mins;
+ hashsig_heap maxs;
+ size_t lines;
+ git_hashsig_option_t opt;
+};
+
+#define HEAP_LCHILD_OF(I) (((I)<<1)+1)
+#define HEAP_RCHILD_OF(I) (((I)<<1)+2)
+#define HEAP_PARENT_OF(I) (((I)-1)>>1)
+
+static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
+{
+ h->size = 0;
+ h->asize = HASHSIG_HEAP_SIZE;
+ h->cmp = cmp;
+}
+
+static int hashsig_cmp_max(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av < bv) ? -1 : (av > bv) ? 1 : 0;
+}
+
+static int hashsig_cmp_min(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av > bv) ? -1 : (av < bv) ? 1 : 0;
+}
+
+static void hashsig_heap_up(hashsig_heap *h, int el)
+{
+ int parent_el = HEAP_PARENT_OF(el);
+
+ while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {
+ hashsig_t t = h->values[el];
+ h->values[el] = h->values[parent_el];
+ h->values[parent_el] = t;
+
+ el = parent_el;
+ parent_el = HEAP_PARENT_OF(el);
+ }
+}
+
+static void hashsig_heap_down(hashsig_heap *h, int el)
+{
+ hashsig_t v, lv, rv;
+
+ /* 'el < h->size / 2' tests if el is bottom row of heap */
+
+ while (el < h->size / 2) {
+ int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel;
+
+ v = h->values[el];
+ lv = h->values[lel];
+ rv = h->values[rel];
+
+ if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)
+ break;
+
+ swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;
+
+ h->values[el] = h->values[swapel];
+ h->values[swapel] = v;
+
+ el = swapel;
+ }
+}
+
+static void hashsig_heap_sort(hashsig_heap *h)
+{
+ /* only need to do this at the end for signature comparison */
+ git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);
+}
+
+static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
+{
+ /* if heap is not full, insert new element */
+ if (h->size < h->asize) {
+ h->values[h->size++] = val;
+ hashsig_heap_up(h, h->size - 1);
+ }
+
+ /* if heap is full, pop top if new element should replace it */
+ else if (h->cmp(&val, &h->values[0], NULL) > 0) {
+ h->size--;
+ h->values[0] = h->values[h->size];
+ hashsig_heap_down(h, 0);
+ }
+
+}
+
+typedef struct {
+ int use_ignores;
+ uint8_t ignore_ch[256];
+} hashsig_in_progress;
+
+static void hashsig_in_progress_init(
+ hashsig_in_progress *prog, git_hashsig *sig)
+{
+ int i;
+
+ /* no more than one can be set */
+ assert(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) ||
+ !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE));
+
+ if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) {
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace_nonlf(i);
+ prog->use_ignores = 1;
+ } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) {
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace(i);
+ prog->use_ignores = 1;
+ } else {
+ memset(prog, 0, sizeof(*prog));
+ }
+}
+
+static int hashsig_add_hashes(
+ git_hashsig *sig,
+ const uint8_t *data,
+ size_t size,
+ hashsig_in_progress *prog)
+{
+ const uint8_t *scan = data, *end = data + size;
+ hashsig_state state = HASHSIG_HASH_START;
+ int use_ignores = prog->use_ignores, len;
+ uint8_t ch;
+
+ while (scan < end) {
+ state = HASHSIG_HASH_START;
+
+ for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) {
+ ch = *scan;
+
+ if (use_ignores)
+ for (; scan < end && git__isspace_nonlf(ch); ch = *scan)
+ ++scan;
+ else if (sig->opt &
+ (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE))
+ for (; scan < end && ch == '\r'; ch = *scan)
+ ++scan;
+
+ /* peek at next character to decide what to do next */
+ if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE)
+ use_ignores = (ch == '\n');
+
+ if (scan >= end)
+ break;
+ ++scan;
+
+ /* check run terminator */
+ if (ch == '\n' || ch == '\0') {
+ sig->lines++;
+ break;
+ }
+
+ ++len;
+ HASHSIG_HASH_MIX(state, ch);
+ }
+
+ if (len > 0) {
+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);
+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
+
+ while (scan < end && (*scan == '\n' || !*scan))
+ ++scan;
+ }
+ }
+
+ prog->use_ignores = use_ignores;
+
+ return 0;
+}
+
+static int hashsig_finalize_hashes(git_hashsig *sig)
+{
+ if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE &&
+ !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) {
+ giterr_set(GITERR_INVALID,
+ "File too small for similarity signature calculation");
+ return GIT_EBUFS;
+ }
+
+ hashsig_heap_sort(&sig->mins);
+ hashsig_heap_sort(&sig->maxs);
+
+ return 0;
+}
+
+static git_hashsig *hashsig_alloc(git_hashsig_option_t opts)
+{
+ git_hashsig *sig = git__calloc(1, sizeof(git_hashsig));
+ if (!sig)
+ return NULL;
+
+ hashsig_heap_init(&sig->mins, hashsig_cmp_min);
+ hashsig_heap_init(&sig->maxs, hashsig_cmp_max);
+ sig->opt = opts;
+
+ return sig;
+}
+
+int git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts)
+{
+ int error;
+ hashsig_in_progress prog;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ hashsig_in_progress_init(&prog, sig);
+
+ error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+int git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts)
+{
+ uint8_t buf[0x1000];
+ ssize_t buflen = 0;
+ int error = 0, fd;
+ hashsig_in_progress prog;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ if ((fd = git_futils_open_ro(path)) < 0) {
+ git__free(sig);
+ return fd;
+ }
+
+ hashsig_in_progress_init(&prog, sig);
+
+ while (!error) {
+ if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
+ if ((error = (int)buflen) < 0)
+ giterr_set(GITERR_OS,
+ "Read error on '%s' calculating similarity hashes", path);
+ break;
+ }
+
+ error = hashsig_add_hashes(sig, buf, buflen, &prog);
+ }
+
+ p_close(fd);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+void git_hashsig_free(git_hashsig *sig)
+{
+ git__free(sig);
+}
+
+static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
+{
+ int matches = 0, i, j, cmp;
+
+ assert(a->cmp == b->cmp);
+
+ /* hash heaps are sorted - just look for overlap vs total */
+
+ for (i = 0, j = 0; i < a->size && j < b->size; ) {
+ cmp = a->cmp(&a->values[i], &b->values[j], NULL);
+
+ if (cmp < 0)
+ ++i;
+ else if (cmp > 0)
+ ++j;
+ else {
+ ++i; ++j; ++matches;
+ }
+ }
+
+ return HASHSIG_SCALE * (matches * 2) / (a->size + b->size);
+}
+
+int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
+{
+ /* if we have no elements in either file then each file is either
+ * empty or blank. if we're ignoring whitespace then the files are
+ * similar, otherwise they're dissimilar.
+ */
+ if (a->mins.size == 0 && b->mins.size == 0) {
+ if ((!a->lines && !b->lines) ||
+ (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE))
+ return HASHSIG_SCALE;
+ else
+ return 0;
+ }
+
+ /* if we have fewer than the maximum number of elements, then just use
+ * one array since the two arrays will be the same
+ */
+ if (a->mins.size < HASHSIG_HEAP_SIZE)
+ return hashsig_heap_compare(&a->mins, &b->mins);
+ else
+ return (hashsig_heap_compare(&a->mins, &b->mins) +
+ hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/sys/filter.h"
+#include "filter.h"
+#include "buffer.h"
+#include "buf_text.h"
+
+static int ident_find_id(
+ const char **id_start, const char **id_end, const char *start, size_t len)
+{
+ const char *end = start + len, *found = NULL;
+
+ while (len > 3 && (found = memchr(start, '$', len)) != NULL) {
+ size_t remaining = (size_t)(end - found) - 1;
+ if (remaining < 3)
+ return GIT_ENOTFOUND;
+
+ start = found + 1;
+ len = remaining;
+
+ if (start[0] == 'I' && start[1] == 'd')
+ break;
+ }
+
+ if (len < 3 || !found)
+ return GIT_ENOTFOUND;
+ *id_start = found;
+
+ if ((found = memchr(start + 2, '$', len - 2)) == NULL)
+ return GIT_ENOTFOUND;
+
+ *id_end = found + 1;
+ return 0;
+}
+
+static int ident_insert_id(
+ git_buf *to, const git_buf *from, const git_filter_source *src)
+{
+ char oid[GIT_OID_HEXSZ+1];
+ const char *id_start, *id_end, *from_end = from->ptr + from->size;
+ size_t need_size;
+
+ /* replace $Id$ with blob id */
+
+ if (!git_filter_source_id(src))
+ return GIT_PASSTHROUGH;
+
+ git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src));
+
+ if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
+ return GIT_PASSTHROUGH;
+
+ need_size = (size_t)(id_start - from->ptr) +
+ 5 /* "$Id: " */ + GIT_OID_HEXSZ + 2 /* " $" */ +
+ (size_t)(from_end - id_end);
+
+ if (git_buf_grow(to, need_size) < 0)
+ return -1;
+
+ git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
+ git_buf_put(to, "$Id: ", 5);
+ git_buf_put(to, oid, GIT_OID_HEXSZ);
+ git_buf_put(to, " $", 2);
+ git_buf_put(to, id_end, (size_t)(from_end - id_end));
+
+ return git_buf_oom(to) ? -1 : 0;
+}
+
+static int ident_remove_id(
+ git_buf *to, const git_buf *from)
+{
+ const char *id_start, *id_end, *from_end = from->ptr + from->size;
+ size_t need_size;
+
+ if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
+ return GIT_PASSTHROUGH;
+
+ need_size = (size_t)(id_start - from->ptr) +
+ 4 /* "$Id$" */ + (size_t)(from_end - id_end);
+
+ if (git_buf_grow(to, need_size) < 0)
+ return -1;
+
+ git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
+ git_buf_put(to, "$Id$", 4);
+ git_buf_put(to, id_end, (size_t)(from_end - id_end));
+
+ return git_buf_oom(to) ? -1 : 0;
+}
+
+static int ident_apply(
+ git_filter *self,
+ void **payload,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ GIT_UNUSED(self); GIT_UNUSED(payload);
+
+ /* Don't filter binary files */
+ if (git_buf_text_is_binary(from))
+ return GIT_PASSTHROUGH;
+
+ if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return ident_insert_id(to, from, src);
+ else
+ return ident_remove_id(to, from);
+}
+
+git_filter *git_ident_filter_new(void)
+{
+ git_filter *f = git__calloc(1, sizeof(git_filter));
+ if (f == NULL)
+ return NULL;
+
+ f->version = GIT_FILTER_VERSION;
+ f->attributes = "+ident"; /* apply to files with ident attribute set */
+ f->shutdown = git_filter_free;
+ f->apply = ident_apply;
+
+ return f;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_idxmap_h__
+#define INCLUDE_idxmap_h__
+
+#include <ctype.h>
+#include "common.h"
+#include "git2/index.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kreallocarray git__reallocarray
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *)
+__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *)
+
+typedef khash_t(idx) git_idxmap;
+typedef khash_t(idxicase) git_idxmap_icase;
+
+typedef khiter_t git_idxmap_iter;
+
+/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */
+static kh_inline khint_t idxentry_hash(const git_index_entry *e)
+{
+ const char *s = e->path;
+ khint_t h = (khint_t)git__tolower(*s);
+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s);
+ return h + GIT_IDXENTRY_STAGE(e);
+}
+
+#define idxentry_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcmp(a->path, b->path) == 0)
+#define idxentry_icase_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0)
+
+#define GIT__USE_IDXMAP \
+ __KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal)
+
+#define GIT__USE_IDXMAP_ICASE \
+ __KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal)
+
+#define git_idxmap_alloc(hp) \
+ ((*(hp) = kh_init(idx)) == NULL) ? giterr_set_oom(), -1 : 0
+
+#define git_idxmap_icase_alloc(hp) \
+ ((*(hp) = kh_init(idxicase)) == NULL) ? giterr_set_oom(), -1 : 0
+
+#define git_idxmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(idx, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_idxmap_icase_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(idxicase, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_idxmap_lookup_index(h, k) kh_get(idx, h, k)
+#define git_idxmap_icase_lookup_index(h, k) kh_get(idxicase, h, k)
+#define git_idxmap_value_at(h, idx) kh_val(h, idx)
+#define git_idxmap_valid_index(h, idx) (idx != kh_end(h))
+#define git_idxmap_has_data(h, idx) kh_exist(h, idx)
+
+#define git_idxmap_resize(h,s) kh_resize(idx, h, s)
+#define git_idxmap_free(h) kh_destroy(idx, h), h = NULL
+#define git_idxmap_clear(h) kh_clear(idx, h)
+
+#define git_idxmap_delete_at(h, id) kh_del(idx, h, id)
+#define git_idxmap_icase_delete_at(h, id) kh_del(idxicase, h, id)
+
+#define git_idxmap_delete(h, key) do { \
+ khiter_t __pos = git_idxmap_lookup_index(h, key); \
+ if (git_idxmap_valid_index(h, __pos)) \
+ git_idxmap_delete_at(h, __pos); } while (0)
+
+#define git_idxmap_icase_delete(h, key) do { \
+ khiter_t __pos = git_idxmap_icase_lookup_index(h, key); \
+ if (git_idxmap_valid_index(h, __pos)) \
+ git_idxmap_icase_delete_at(h, __pos); } while (0)
+
+#define git_idxmap_begin kh_begin
+#define git_idxmap_end kh_end
+
+#endif
--- /dev/null
+#include "git2/ignore.h"
+#include "common.h"
+#include "ignore.h"
+#include "attrcache.h"
+#include "path.h"
+#include "config.h"
+#include "fnmatch.h"
+
+#define GIT_IGNORE_INTERNAL "[internal]exclude"
+
+#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
+
+/**
+ * A negative ignore pattern can negate a positive one without
+ * wildcards if it is a basename only and equals the basename of
+ * the positive pattern. Thus
+ *
+ * foo/bar
+ * !bar
+ *
+ * would result in foo/bar being unignored again while
+ *
+ * moo/foo/bar
+ * !foo/bar
+ *
+ * would do nothing. The reverse also holds true: a positive
+ * basename pattern can be negated by unignoring the basename in
+ * subdirectories. Thus
+ *
+ * bar
+ * !foo/bar
+ *
+ * would result in foo/bar being unignored again. As with the
+ * first case,
+ *
+ * foo/bar
+ * !moo/foo/bar
+ *
+ * would do nothing, again.
+ */
+static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
+{
+ git_attr_fnmatch *longer, *shorter;
+ char *p;
+
+ if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0
+ && (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) {
+
+ /* If lengths match we need to have an exact match */
+ if (rule->length == neg->length) {
+ return strcmp(rule->pattern, neg->pattern) == 0;
+ } else if (rule->length < neg->length) {
+ shorter = rule;
+ longer = neg;
+ } else {
+ shorter = neg;
+ longer = rule;
+ }
+
+ /* Otherwise, we need to check if the shorter
+ * rule is a basename only (that is, it contains
+ * no path separator) and, if so, if it
+ * matches the tail of the longer rule */
+ p = longer->pattern + longer->length - shorter->length;
+
+ if (p[-1] != '/')
+ return false;
+ if (memchr(shorter->pattern, '/', shorter->length) != NULL)
+ return false;
+
+ return memcmp(p, shorter->pattern, shorter->length) == 0;
+ }
+
+ return false;
+}
+
+/**
+ * A negative ignore can only unignore a file which is given explicitly before, thus
+ *
+ * foo
+ * !foo/bar
+ *
+ * does not unignore 'foo/bar' as it's not in the list. However
+ *
+ * foo/<star>
+ * !foo/bar
+ *
+ * does unignore 'foo/bar', as it is contained within the 'foo/<star>' rule.
+ */
+static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
+{
+ int error = 0;
+ size_t i;
+ git_attr_fnmatch *rule;
+ char *path;
+ git_buf buf = GIT_BUF_INIT;
+
+ *out = 0;
+
+ /* path of the file relative to the workdir, so we match the rules in subdirs */
+ if (match->containing_dir) {
+ git_buf_puts(&buf, match->containing_dir);
+ }
+ if (git_buf_puts(&buf, match->pattern) < 0)
+ return -1;
+
+ path = git_buf_detach(&buf);
+
+ git_vector_foreach(rules, i, rule) {
+ if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
+ if (does_negate_pattern(rule, match)) {
+ error = 0;
+ *out = 1;
+ goto out;
+ }
+ else
+ continue;
+ }
+
+ /*
+ * When dealing with a directory, we add '/<star>' so
+ * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR
+ * alone isn't enough as that's also set for nagations, so we
+ * need to check that NEGATIVE is off.
+ */
+ git_buf_clear(&buf);
+ if (rule->containing_dir) {
+ git_buf_puts(&buf, rule->containing_dir);
+ }
+
+ error = git_buf_puts(&buf, rule->pattern);
+
+ if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR)
+ error = git_buf_PUTS(&buf, "/*");
+
+ if (error < 0)
+ goto out;
+
+ if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) {
+ giterr_set(GITERR_INVALID, "error matching pattern");
+ goto out;
+ }
+
+ /* if we found a match, we want to keep this rule */
+ if (error != FNM_NOMATCH) {
+ *out = 1;
+ error = 0;
+ goto out;
+ }
+ }
+
+ error = 0;
+
+out:
+ git__free(path);
+ git_buf_free(&buf);
+ return error;
+}
+
+static int parse_ignore_file(
+ git_repository *repo, git_attr_file *attrs, const char *data)
+{
+ int error = 0;
+ int ignore_case = false;
+ const char *scan = data, *context = NULL;
+ git_attr_fnmatch *match = NULL;
+
+ if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
+ giterr_clear();
+
+ /* if subdir file path, convert context for file paths */
+ if (attrs->entry &&
+ git_path_root(attrs->entry->path) < 0 &&
+ !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE))
+ context = attrs->entry->path;
+
+ if (git_mutex_lock(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock ignore file");
+ return -1;
+ }
+
+ while (!error && *scan) {
+ int valid_rule = 1;
+
+ if (!match && !(match = git__calloc(1, sizeof(*match)))) {
+ error = -1;
+ break;
+ }
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
+
+ if (!(error = git_attr_fnmatch__parse(
+ match, &attrs->pool, context, &scan)))
+ {
+ match->flags |= GIT_ATTR_FNMATCH_IGNORE;
+
+ if (ignore_case)
+ match->flags |= GIT_ATTR_FNMATCH_ICASE;
+
+ scan = git__next_line(scan);
+
+ /* if a negative match doesn't actually do anything, throw it away */
+ if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ error = does_negate_rule(&valid_rule, &attrs->rules, match);
+
+ if (!error && valid_rule)
+ error = git_vector_insert(&attrs->rules, match);
+ }
+
+ if (error != 0 || !valid_rule) {
+ match->pattern = NULL;
+
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ } else {
+ match = NULL; /* vector now "owns" the match */
+ }
+ }
+
+ git_mutex_unlock(&attrs->lock);
+ git__free(match);
+
+ return error;
+}
+
+static int push_ignore_file(
+ git_ignores *ignores,
+ git_vector *which_list,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_attr_file *file = NULL;
+
+ error = git_attr_cache__get(
+ &file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
+ base, filename, parse_ignore_file);
+ if (error < 0)
+ return error;
+
+ if (file != NULL) {
+ if ((error = git_vector_insert(which_list, file)) < 0)
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+static int push_one_ignore(void *payload, const char *path)
+{
+ git_ignores *ign = payload;
+ ign->depth++;
+ return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE);
+}
+
+static int get_internal_ignores(git_attr_file **out, git_repository *repo)
+{
+ int error;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ error = git_attr_cache__get(
+ out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
+
+ /* if internal rules list is empty, insert default rules */
+ if (!error && !(*out)->rules.length)
+ error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
+
+ return error;
+}
+
+int git_ignore__for_path(
+ git_repository *repo,
+ const char *path,
+ git_ignores *ignores)
+{
+ int error = 0;
+ const char *workdir = git_repository_workdir(repo);
+
+ assert(ignores && path);
+
+ memset(ignores, 0, sizeof(*ignores));
+ ignores->repo = repo;
+
+ /* Read the ignore_case flag */
+ if ((error = git_repository__cvar(
+ &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
+ goto cleanup;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ goto cleanup;
+
+ /* given a unrooted path in a non-bare repo, resolve it */
+ if (workdir && git_path_root(path) < 0) {
+ git_buf local = GIT_BUF_INIT;
+
+ if ((error = git_path_dirname_r(&local, path)) < 0 ||
+ (error = git_path_resolve_relative(&local, 0)) < 0 ||
+ (error = git_path_to_dir(&local)) < 0 ||
+ (error = git_buf_joinpath(&ignores->dir, workdir, local.ptr)) < 0)
+ {;} /* Nothing, we just want to stop on the first error */
+ git_buf_free(&local);
+ } else {
+ error = git_buf_joinpath(&ignores->dir, path, "");
+ }
+ if (error < 0)
+ goto cleanup;
+
+ if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
+ ignores->dir_root = strlen(workdir);
+
+ /* set up internals */
+ if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
+ goto cleanup;
+
+ /* load .gitignore up the path */
+ if (workdir != NULL) {
+ error = git_path_walk_up(
+ &ignores->dir, workdir, push_one_ignore, ignores);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ /* load .git/info/exclude */
+ error = push_ignore_file(
+ ignores, &ignores->ign_global,
+ git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
+ if (error < 0)
+ goto cleanup;
+
+ /* load core.excludesfile */
+ if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
+ error = push_ignore_file(
+ ignores, &ignores->ign_global, NULL,
+ git_repository_attr_cache(repo)->cfg_excl_file);
+
+cleanup:
+ if (error < 0)
+ git_ignore__free(ignores);
+
+ return error;
+}
+
+int git_ignore__push_dir(git_ignores *ign, const char *dir)
+{
+ if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
+ return -1;
+
+ ign->depth++;
+
+ return push_ignore_file(
+ ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
+}
+
+int git_ignore__pop_dir(git_ignores *ign)
+{
+ if (ign->ign_path.length > 0) {
+ git_attr_file *file = git_vector_last(&ign->ign_path);
+ const char *start = file->entry->path, *end;
+
+ /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
+ * - file->path looks something like "a/b/.gitignore
+ *
+ * We are popping the last directory off ign->dir. We also want
+ * to remove the file from the vector if the popped directory
+ * matches the ignore path. We need to test if the "a/b" part of
+ * the file key matches the path we are about to pop.
+ */
+
+ if ((end = strrchr(start, '/')) != NULL) {
+ size_t dirlen = (end - start) + 1;
+ const char *relpath = ign->dir.ptr + ign->dir_root;
+ size_t pathlen = ign->dir.size - ign->dir_root;
+
+ if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
+ git_vector_pop(&ign->ign_path);
+ git_attr_file__free(file);
+ }
+ }
+ }
+
+ if (--ign->depth > 0) {
+ git_buf_rtruncate_at_char(&ign->dir, '/');
+ git_path_to_dir(&ign->dir);
+ }
+
+ return 0;
+}
+
+void git_ignore__free(git_ignores *ignores)
+{
+ unsigned int i;
+ git_attr_file *file;
+
+ git_attr_file__free(ignores->ign_internal);
+
+ git_vector_foreach(&ignores->ign_path, i, file) {
+ git_attr_file__free(file);
+ ignores->ign_path.contents[i] = NULL;
+ }
+ git_vector_free(&ignores->ign_path);
+
+ git_vector_foreach(&ignores->ign_global, i, file) {
+ git_attr_file__free(file);
+ ignores->ign_global.contents[i] = NULL;
+ }
+ git_vector_free(&ignores->ign_global);
+
+ git_buf_free(&ignores->dir);
+}
+
+static bool ignore_lookup_in_rules(
+ int *ignored, git_attr_file *file, git_attr_path *path)
+{
+ size_t j;
+ git_attr_fnmatch *match;
+
+ git_vector_rforeach(&file->rules, j, match) {
+ if (git_attr_fnmatch__match(match, path)) {
+ *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
+ GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int git_ignore__lookup(
+ int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
+{
+ unsigned int i;
+ git_attr_file *file;
+ git_attr_path path;
+
+ *out = GIT_IGNORE_NOTFOUND;
+
+ if (git_attr_path__init(
+ &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
+ return -1;
+
+ /* first process builtins - success means path was found */
+ if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
+ goto cleanup;
+
+ /* next process files in the path */
+ git_vector_foreach(&ignores->ign_path, i, file) {
+ if (ignore_lookup_in_rules(out, file, &path))
+ goto cleanup;
+ }
+
+ /* last process global ignores */
+ git_vector_foreach(&ignores->ign_global, i, file) {
+ if (ignore_lookup_in_rules(out, file, &path))
+ goto cleanup;
+ }
+
+cleanup:
+ git_attr_path__free(&path);
+ return 0;
+}
+
+int git_ignore_add_rule(git_repository *repo, const char *rules)
+{
+ int error;
+ git_attr_file *ign_internal = NULL;
+
+ if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
+ return error;
+
+ error = parse_ignore_file(repo, ign_internal, rules);
+ git_attr_file__free(ign_internal);
+
+ return error;
+}
+
+int git_ignore_clear_internal_rules(git_repository *repo)
+{
+ int error;
+ git_attr_file *ign_internal;
+
+ if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
+ return error;
+
+ if (!(error = git_attr_file__clear_rules(ign_internal, true)))
+ error = parse_ignore_file(
+ repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
+
+ git_attr_file__free(ign_internal);
+ return error;
+}
+
+int git_ignore_path_is_ignored(
+ int *ignored,
+ git_repository *repo,
+ const char *pathname)
+{
+ int error;
+ const char *workdir;
+ git_attr_path path;
+ git_ignores ignores;
+ unsigned int i;
+ git_attr_file *file;
+
+ assert(ignored && pathname);
+
+ workdir = repo ? git_repository_workdir(repo) : NULL;
+
+ memset(&path, 0, sizeof(path));
+ memset(&ignores, 0, sizeof(ignores));
+
+ if ((error = git_attr_path__init(&path, pathname, workdir, GIT_DIR_FLAG_UNKNOWN)) < 0 ||
+ (error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
+ goto cleanup;
+
+ while (1) {
+ /* first process builtins - success means path was found */
+ if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path))
+ goto cleanup;
+
+ /* next process files in the path */
+ git_vector_foreach(&ignores.ign_path, i, file) {
+ if (ignore_lookup_in_rules(ignored, file, &path))
+ goto cleanup;
+ }
+
+ /* last process global ignores */
+ git_vector_foreach(&ignores.ign_global, i, file) {
+ if (ignore_lookup_in_rules(ignored, file, &path))
+ goto cleanup;
+ }
+
+ /* move up one directory */
+ if (path.basename == path.path)
+ break;
+ path.basename[-1] = '\0';
+ while (path.basename > path.path && *path.basename != '/')
+ path.basename--;
+ if (path.basename > path.path)
+ path.basename++;
+ path.is_dir = 1;
+
+ if ((error = git_ignore__pop_dir(&ignores)) < 0)
+ break;
+ }
+
+ *ignored = 0;
+
+cleanup:
+ git_attr_path__free(&path);
+ git_ignore__free(&ignores);
+ return error;
+}
+
+int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo,
+ git_vector *vspec,
+ bool no_fnmatch)
+{
+ int error = 0;
+ size_t i;
+ git_attr_fnmatch *match;
+ int ignored;
+ git_buf path = GIT_BUF_INIT;
+ const char *wd, *filename;
+ git_index *idx;
+
+ if ((error = git_repository__ensure_not_bare(
+ repo, "validate pathspec")) < 0 ||
+ (error = git_repository_index(&idx, repo)) < 0)
+ return error;
+
+ wd = git_repository_workdir(repo);
+
+ git_vector_foreach(vspec, i, match) {
+ /* skip wildcard matches (if they are being used) */
+ if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
+ !no_fnmatch)
+ continue;
+
+ filename = match->pattern;
+
+ /* if file is already in the index, it's fine */
+ if (git_index_get_bypath(idx, filename, 0) != NULL)
+ continue;
+
+ if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
+ break;
+
+ /* is there a file on disk that matches this exactly? */
+ if (!git_path_isfile(path.ptr))
+ continue;
+
+ /* is that file ignored? */
+ if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
+ break;
+
+ if (ignored) {
+ giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
+ filename);
+ error = GIT_EINVALIDSPEC;
+ break;
+ }
+ }
+
+ git_index_free(idx);
+ git_buf_free(&path);
+
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_ignore_h__
+#define INCLUDE_ignore_h__
+
+#include "repository.h"
+#include "vector.h"
+#include "attr_file.h"
+
+#define GIT_IGNORE_FILE ".gitignore"
+#define GIT_IGNORE_FILE_INREPO "info/exclude"
+#define GIT_IGNORE_FILE_XDG "ignore"
+
+/* The git_ignores structure maintains three sets of ignores:
+ * - internal ignores
+ * - per directory ignores
+ * - global ignores (at lower priority than the others)
+ * As you traverse from one directory to another, you can push and pop
+ * directories onto git_ignores list efficiently.
+ */
+typedef struct {
+ git_repository *repo;
+ git_buf dir; /* current directory reflected in ign_path */
+ git_attr_file *ign_internal;
+ git_vector ign_path;
+ git_vector ign_global;
+ size_t dir_root; /* offset in dir to repo root */
+ int ignore_case;
+ int depth;
+} git_ignores;
+
+extern int git_ignore__for_path(
+ git_repository *repo, const char *path, git_ignores *ign);
+
+extern int git_ignore__push_dir(git_ignores *ign, const char *dir);
+
+extern int git_ignore__pop_dir(git_ignores *ign);
+
+extern void git_ignore__free(git_ignores *ign);
+
+enum {
+ GIT_IGNORE_UNCHECKED = -2,
+ GIT_IGNORE_NOTFOUND = -1,
+ GIT_IGNORE_FALSE = 0,
+ GIT_IGNORE_TRUE = 1,
+};
+
+extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag);
+
+/* command line Git sometimes generates an error message if given a
+ * pathspec that contains an exact match to an ignored file (provided
+ * --force isn't also given). This makes it easy to check it that has
+ * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored
+ * exact matches (that are not already present in the index).
+ */
+extern int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo, git_vector *pathspec, bool no_fnmatch);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stddef.h>
+
+#include "common.h"
+#include "repository.h"
+#include "index.h"
+#include "tree.h"
+#include "tree-cache.h"
+#include "hash.h"
+#include "iterator.h"
+#include "pathspec.h"
+#include "ignore.h"
+#include "blob.h"
+#include "idxmap.h"
+#include "diff.h"
+#include "varint.h"
+
+#include "git2/odb.h"
+#include "git2/oid.h"
+#include "git2/blob.h"
+#include "git2/config.h"
+#include "git2/sys/index.h"
+
+GIT__USE_IDXMAP
+GIT__USE_IDXMAP_ICASE
+
+#define INSERT_IN_MAP_EX(idx, map, e, err) do { \
+ if ((idx)->ignore_case) \
+ git_idxmap_icase_insert((khash_t(idxicase) *) (map), (e), (e), (err)); \
+ else \
+ git_idxmap_insert((map), (e), (e), (err)); \
+ } while (0)
+
+#define INSERT_IN_MAP(idx, e, err) INSERT_IN_MAP_EX(idx, (idx)->entries_map, e, err)
+
+#define LOOKUP_IN_MAP(p, idx, k) do { \
+ if ((idx)->ignore_case) \
+ (p) = git_idxmap_icase_lookup_index((khash_t(idxicase) *) index->entries_map, (k)); \
+ else \
+ (p) = git_idxmap_lookup_index(index->entries_map, (k)); \
+ } while (0)
+
+#define DELETE_IN_MAP(idx, e) do { \
+ if ((idx)->ignore_case) \
+ git_idxmap_icase_delete((khash_t(idxicase) *) (idx)->entries_map, (e)); \
+ else \
+ git_idxmap_delete((idx)->entries_map, (e)); \
+ } while (0)
+
+static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths,
+ unsigned int flags,
+ git_index_matched_path_cb cb, void *payload);
+
+#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
+#define short_entry_size(len) entry_size(struct entry_short, len)
+#define long_entry_size(len) entry_size(struct entry_long, len)
+
+#define minimal_entry_size (offsetof(struct entry_short, path))
+
+static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ;
+static const size_t INDEX_HEADER_SIZE = 12;
+
+static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2;
+static const unsigned int INDEX_VERSION_NUMBER_LB = 2;
+static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;
+static const unsigned int INDEX_VERSION_NUMBER_COMP = 4;
+static const unsigned int INDEX_VERSION_NUMBER_UB = 4;
+
+static const unsigned int INDEX_HEADER_SIG = 0x44495243;
+static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};
+static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'};
+static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'};
+
+#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx)))
+
+struct index_header {
+ uint32_t signature;
+ uint32_t version;
+ uint32_t entry_count;
+};
+
+struct index_extension {
+ char signature[4];
+ uint32_t extension_size;
+};
+
+struct entry_time {
+ uint32_t seconds;
+ uint32_t nanoseconds;
+};
+
+struct entry_short {
+ struct entry_time ctime;
+ struct entry_time mtime;
+ uint32_t dev;
+ uint32_t ino;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t file_size;
+ git_oid oid;
+ uint16_t flags;
+ char path[1]; /* arbitrary length */
+};
+
+struct entry_long {
+ struct entry_time ctime;
+ struct entry_time mtime;
+ uint32_t dev;
+ uint32_t ino;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t file_size;
+ git_oid oid;
+ uint16_t flags;
+ uint16_t flags_extended;
+ char path[1]; /* arbitrary length */
+};
+
+struct entry_srch_key {
+ const char *path;
+ size_t pathlen;
+ int stage;
+};
+
+struct entry_internal {
+ git_index_entry entry;
+ size_t pathlen;
+ char path[GIT_FLEX_ARRAY];
+};
+
+struct reuc_entry_internal {
+ git_index_reuc_entry entry;
+ size_t pathlen;
+ char path[GIT_FLEX_ARRAY];
+};
+
+/* local declarations */
+static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
+static int read_header(struct index_header *dest, const void *buffer);
+
+static int parse_index(git_index *index, const char *buffer, size_t buffer_size);
+static bool is_index_extended(git_index *index);
+static int write_index(git_oid *checksum, git_index *index, git_filebuf *file);
+
+static void index_entry_free(git_index_entry *entry);
+static void index_entry_reuc_free(git_index_reuc_entry *reuc);
+
+int git_index_entry_srch(const void *key, const void *array_member)
+{
+ const struct entry_srch_key *srch_key = key;
+ const struct entry_internal *entry = array_member;
+ int cmp;
+ size_t len1, len2, len;
+
+ len1 = srch_key->pathlen;
+ len2 = entry->pathlen;
+ len = len1 < len2 ? len1 : len2;
+
+ cmp = memcmp(srch_key->path, entry->path, len);
+ if (cmp)
+ return cmp;
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+
+ if (srch_key->stage != GIT_INDEX_STAGE_ANY)
+ return srch_key->stage - GIT_IDXENTRY_STAGE(&entry->entry);
+
+ return 0;
+}
+
+int git_index_entry_isrch(const void *key, const void *array_member)
+{
+ const struct entry_srch_key *srch_key = key;
+ const struct entry_internal *entry = array_member;
+ int cmp;
+ size_t len1, len2, len;
+
+ len1 = srch_key->pathlen;
+ len2 = entry->pathlen;
+ len = len1 < len2 ? len1 : len2;
+
+ cmp = strncasecmp(srch_key->path, entry->path, len);
+
+ if (cmp)
+ return cmp;
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+
+ if (srch_key->stage != GIT_INDEX_STAGE_ANY)
+ return srch_key->stage - GIT_IDXENTRY_STAGE(&entry->entry);
+
+ return 0;
+}
+
+static int index_entry_srch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcmp((const char *)path, entry->path);
+}
+
+static int index_entry_isrch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcasecmp((const char *)path, entry->path);
+}
+
+int git_index_entry_cmp(const void *a, const void *b)
+{
+ int diff;
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ diff = strcmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
+
+ return diff;
+}
+
+int git_index_entry_icmp(const void *a, const void *b)
+{
+ int diff;
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ diff = strcasecmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
+
+ return diff;
+}
+
+static int conflict_name_cmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcmp(name_a->ours, name_b->ours);
+}
+
+/**
+ * TODO: enable this when resolving case insensitive conflicts
+ */
+#if 0
+static int conflict_name_icmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcasecmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcasecmp(name_a->ours, name_b->ours);
+}
+#endif
+
+static int reuc_srch(const void *key, const void *array_member)
+{
+ const git_index_reuc_entry *reuc = array_member;
+
+ return strcmp(key, reuc->path);
+}
+
+static int reuc_isrch(const void *key, const void *array_member)
+{
+ const git_index_reuc_entry *reuc = array_member;
+
+ return strcasecmp(key, reuc->path);
+}
+
+static int reuc_cmp(const void *a, const void *b)
+{
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
+
+ return strcmp(info_a->path, info_b->path);
+}
+
+static int reuc_icmp(const void *a, const void *b)
+{
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
+
+ return strcasecmp(info_a->path, info_b->path);
+}
+
+static void index_entry_reuc_free(git_index_reuc_entry *reuc)
+{
+ git__free(reuc);
+}
+
+static void index_entry_free(git_index_entry *entry)
+{
+ if (!entry)
+ return;
+
+ memset(&entry->id, 0, sizeof(entry->id));
+ git__free(entry);
+}
+
+unsigned int git_index__create_mode(unsigned int mode)
+{
+ if (S_ISLNK(mode))
+ return S_IFLNK;
+
+ if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))
+ return (S_IFLNK | S_IFDIR);
+
+ return S_IFREG | GIT_PERMS_CANONICAL(mode);
+}
+
+static unsigned int index_merge_mode(
+ git_index *index, git_index_entry *existing, unsigned int mode)
+{
+ if (index->no_symlinks && S_ISREG(mode) &&
+ existing && S_ISLNK(existing->mode))
+ return existing->mode;
+
+ if (index->distrust_filemode && S_ISREG(mode))
+ return (existing && S_ISREG(existing->mode)) ?
+ existing->mode : git_index__create_mode(0666);
+
+ return git_index__create_mode(mode);
+}
+
+GIT_INLINE(int) index_find_in_entries(
+ size_t *out, git_vector *entries, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage)
+{
+ struct entry_srch_key srch_key;
+ srch_key.path = path;
+ srch_key.pathlen = !path_len ? strlen(path) : path_len;
+ srch_key.stage = stage;
+ return git_vector_bsearch2(out, entries, entry_srch, &srch_key);
+}
+
+GIT_INLINE(int) index_find(
+ size_t *out, git_index *index,
+ const char *path, size_t path_len, int stage)
+{
+ git_vector_sort(&index->entries);
+
+ return index_find_in_entries(
+ out, &index->entries, index->entries_search, path, path_len, stage);
+}
+
+void git_index__set_ignore_case(git_index *index, bool ignore_case)
+{
+ index->ignore_case = ignore_case;
+
+ if (ignore_case) {
+ index->entries_cmp_path = git__strcasecmp_cb;
+ index->entries_search = git_index_entry_isrch;
+ index->entries_search_path = index_entry_isrch_path;
+ index->reuc_search = reuc_isrch;
+ } else {
+ index->entries_cmp_path = git__strcmp_cb;
+ index->entries_search = git_index_entry_srch;
+ index->entries_search_path = index_entry_srch_path;
+ index->reuc_search = reuc_srch;
+ }
+
+ git_vector_set_cmp(&index->entries,
+ ignore_case ? git_index_entry_icmp : git_index_entry_cmp);
+ git_vector_sort(&index->entries);
+
+ git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp);
+ git_vector_sort(&index->reuc);
+}
+
+int git_index_open(git_index **index_out, const char *index_path)
+{
+ git_index *index;
+ int error = -1;
+
+ assert(index_out);
+
+ index = git__calloc(1, sizeof(git_index));
+ GITERR_CHECK_ALLOC(index);
+
+ git_pool_init(&index->tree_pool, 1);
+
+ if (index_path != NULL) {
+ index->index_file_path = git__strdup(index_path);
+ if (!index->index_file_path)
+ goto fail;
+
+ /* Check if index file is stored on disk already */
+ if (git_path_exists(index->index_file_path) == true)
+ index->on_disk = 1;
+ }
+
+ if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 ||
+ git_idxmap_alloc(&index->entries_map) < 0 ||
+ git_vector_init(&index->names, 8, conflict_name_cmp) < 0 ||
+ git_vector_init(&index->reuc, 8, reuc_cmp) < 0 ||
+ git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0)
+ goto fail;
+
+ index->entries_cmp_path = git__strcmp_cb;
+ index->entries_search = git_index_entry_srch;
+ index->entries_search_path = index_entry_srch_path;
+ index->reuc_search = reuc_srch;
+ index->version = INDEX_VERSION_NUMBER_DEFAULT;
+
+ if (index_path != NULL && (error = git_index_read(index, true)) < 0)
+ goto fail;
+
+ *index_out = index;
+ GIT_REFCOUNT_INC(index);
+
+ return 0;
+
+fail:
+ git_pool_clear(&index->tree_pool);
+ git_index_free(index);
+ return error;
+}
+
+int git_index_new(git_index **out)
+{
+ return git_index_open(out, NULL);
+}
+
+static void index_free(git_index *index)
+{
+ /* index iterators increment the refcount of the index, so if we
+ * get here then there should be no outstanding iterators.
+ */
+ assert(!git_atomic_get(&index->readers));
+
+ git_index_clear(index);
+ git_idxmap_free(index->entries_map);
+ git_vector_free(&index->entries);
+ git_vector_free(&index->names);
+ git_vector_free(&index->reuc);
+ git_vector_free(&index->deleted);
+
+ git__free(index->index_file_path);
+
+ git__memzero(index, sizeof(*index));
+ git__free(index);
+}
+
+void git_index_free(git_index *index)
+{
+ if (index == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(index, index_free);
+}
+
+/* call with locked index */
+static void index_free_deleted(git_index *index)
+{
+ int readers = (int)git_atomic_get(&index->readers);
+ size_t i;
+
+ if (readers > 0 || !index->deleted.length)
+ return;
+
+ for (i = 0; i < index->deleted.length; ++i) {
+ git_index_entry *ie = git__swap(index->deleted.contents[i], NULL);
+ index_entry_free(ie);
+ }
+
+ git_vector_clear(&index->deleted);
+}
+
+/* call with locked index */
+static int index_remove_entry(git_index *index, size_t pos)
+{
+ int error = 0;
+ git_index_entry *entry = git_vector_get(&index->entries, pos);
+
+ if (entry != NULL) {
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ DELETE_IN_MAP(index, entry);
+ }
+
+ error = git_vector_remove(&index->entries, pos);
+
+ if (!error) {
+ if (git_atomic_get(&index->readers) > 0) {
+ error = git_vector_insert(&index->deleted, entry);
+ } else {
+ index_entry_free(entry);
+ }
+ }
+
+ return error;
+}
+
+int git_index_clear(git_index *index)
+{
+ int error = 0;
+
+ assert(index);
+
+ index->tree = NULL;
+ git_pool_clear(&index->tree_pool);
+
+ git_idxmap_clear(index->entries_map);
+ while (!error && index->entries.length > 0)
+ error = index_remove_entry(index, index->entries.length - 1);
+ index_free_deleted(index);
+
+ git_index_reuc_clear(index);
+ git_index_name_clear(index);
+
+ git_futils_filestamp_set(&index->stamp, NULL);
+
+ return error;
+}
+
+static int create_index_error(int error, const char *msg)
+{
+ giterr_set_str(GITERR_INDEX, msg);
+ return error;
+}
+
+int git_index_set_caps(git_index *index, int caps)
+{
+ unsigned int old_ignore_case;
+
+ assert(index);
+
+ old_ignore_case = index->ignore_case;
+
+ if (caps == GIT_INDEXCAP_FROM_OWNER) {
+ git_repository *repo = INDEX_OWNER(index);
+ int val;
+
+ if (!repo)
+ return create_index_error(
+ -1, "Cannot access repository to set index caps");
+
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORECASE))
+ index->ignore_case = (val != 0);
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE))
+ index->distrust_filemode = (val == 0);
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS))
+ index->no_symlinks = (val == 0);
+ }
+ else {
+ index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
+ index->distrust_filemode = ((caps & GIT_INDEXCAP_NO_FILEMODE) != 0);
+ index->no_symlinks = ((caps & GIT_INDEXCAP_NO_SYMLINKS) != 0);
+ }
+
+ if (old_ignore_case != index->ignore_case) {
+ git_index__set_ignore_case(index, (bool)index->ignore_case);
+ }
+
+ return 0;
+}
+
+int git_index_caps(const git_index *index)
+{
+ return ((index->ignore_case ? GIT_INDEXCAP_IGNORE_CASE : 0) |
+ (index->distrust_filemode ? GIT_INDEXCAP_NO_FILEMODE : 0) |
+ (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0));
+}
+
+const git_oid *git_index_checksum(git_index *index)
+{
+ return &index->checksum;
+}
+
+/**
+ * Returns 1 for changed, 0 for not changed and <0 for errors
+ */
+static int compare_checksum(git_index *index)
+{
+ int fd;
+ ssize_t bytes_read;
+ git_oid checksum = {{ 0 }};
+
+ if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0)
+ return fd;
+
+ if (p_lseek(fd, -20, SEEK_END) < 0) {
+ p_close(fd);
+ giterr_set(GITERR_OS, "failed to seek to end of file");
+ return -1;
+ }
+
+ bytes_read = p_read(fd, &checksum, GIT_OID_RAWSZ);
+ p_close(fd);
+
+ if (bytes_read < 0)
+ return -1;
+
+ return !!git_oid_cmp(&checksum, &index->checksum);
+}
+
+int git_index_read(git_index *index, int force)
+{
+ int error = 0, updated;
+ git_buf buffer = GIT_BUF_INIT;
+ git_futils_filestamp stamp = index->stamp;
+
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
+
+ index->on_disk = git_path_exists(index->index_file_path);
+
+ if (!index->on_disk) {
+ if (force)
+ return git_index_clear(index);
+ return 0;
+ }
+
+ if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) ||
+ ((updated = compare_checksum(index)) < 0)) {
+ giterr_set(
+ GITERR_INDEX,
+ "Failed to read index: '%s' no longer exists",
+ index->index_file_path);
+ return updated;
+ }
+ if (!updated && !force)
+ return 0;
+
+ error = git_futils_readbuffer(&buffer, index->index_file_path);
+ if (error < 0)
+ return error;
+
+ index->tree = NULL;
+ git_pool_clear(&index->tree_pool);
+
+ error = git_index_clear(index);
+
+ if (!error)
+ error = parse_index(index, buffer.ptr, buffer.size);
+
+ if (!error)
+ git_futils_filestamp_set(&index->stamp, &stamp);
+
+ git_buf_free(&buffer);
+ return error;
+}
+
+int git_index__changed_relative_to(
+ git_index *index, const git_oid *checksum)
+{
+ /* attempt to update index (ignoring errors) */
+ if (git_index_read(index, false) < 0)
+ giterr_clear();
+
+ return !!git_oid_cmp(&index->checksum, checksum);
+}
+
+static bool is_racy_entry(git_index *index, const git_index_entry *entry)
+{
+ /* Git special-cases submodules in the check */
+ if (S_ISGITLINK(entry->mode))
+ return false;
+
+ return git_index_entry_newer_than_index(entry, index);
+}
+
+/*
+ * Force the next diff to take a look at those entries which have the
+ * same timestamp as the current index.
+ */
+static int truncate_racily_clean(git_index *index)
+{
+ size_t i;
+ int error;
+ git_index_entry *entry;
+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_vector paths = GIT_VECTOR_INIT;
+ git_diff_delta *delta;
+
+ /* Nothing to do if there's no repo to talk about */
+ if (!INDEX_OWNER(index))
+ return 0;
+
+ /* If there's no workdir, we can't know where to even check */
+ if (!git_repository_workdir(INDEX_OWNER(index)))
+ return 0;
+
+ diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ git_vector_foreach(&index->entries, i, entry) {
+ if ((entry->flags_extended & GIT_IDXENTRY_UPTODATE) == 0 &&
+ is_racy_entry(index, entry))
+ git_vector_insert(&paths, (char *)entry->path);
+ }
+
+ if (paths.length == 0)
+ goto done;
+
+ diff_opts.pathspec.count = paths.length;
+ diff_opts.pathspec.strings = (char **)paths.contents;
+
+ if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0)
+ return error;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0);
+
+ /* Ensure that we have a stage 0 for this file (ie, it's not a
+ * conflict), otherwise smudging it is quite pointless.
+ */
+ if (entry)
+ entry->file_size = 0;
+ }
+
+done:
+ git_diff_free(diff);
+ git_vector_free(&paths);
+ return 0;
+}
+
+unsigned git_index_version(git_index *index)
+{
+ assert(index);
+
+ return index->version;
+}
+
+int git_index_set_version(git_index *index, unsigned int version)
+{
+ assert(index);
+
+ if (version < INDEX_VERSION_NUMBER_LB ||
+ version > INDEX_VERSION_NUMBER_UB) {
+ giterr_set(GITERR_INDEX, "Invalid version number");
+ return -1;
+ }
+
+ index->version = version;
+
+ return 0;
+}
+
+int git_index_write(git_index *index)
+{
+ git_indexwriter writer = GIT_INDEXWRITER_INIT;
+ int error;
+
+ truncate_racily_clean(index);
+
+ if ((error = git_indexwriter_init(&writer, index)) == 0)
+ error = git_indexwriter_commit(&writer);
+
+ git_indexwriter_cleanup(&writer);
+
+ return error;
+}
+
+const char * git_index_path(const git_index *index)
+{
+ assert(index);
+ return index->index_file_path;
+}
+
+int git_index_write_tree(git_oid *oid, git_index *index)
+{
+ git_repository *repo;
+
+ assert(oid && index);
+
+ repo = INDEX_OWNER(index);
+
+ if (repo == NULL)
+ return create_index_error(-1, "Failed to write tree. "
+ "The index file is not backed up by an existing repository");
+
+ return git_tree__write_index(oid, index, repo);
+}
+
+int git_index_write_tree_to(
+ git_oid *oid, git_index *index, git_repository *repo)
+{
+ assert(oid && index && repo);
+ return git_tree__write_index(oid, index, repo);
+}
+
+size_t git_index_entrycount(const git_index *index)
+{
+ assert(index);
+ return index->entries.length;
+}
+
+const git_index_entry *git_index_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+ git_vector_sort(&index->entries);
+ return git_vector_get(&index->entries, n);
+}
+
+const git_index_entry *git_index_get_bypath(
+ git_index *index, const char *path, int stage)
+{
+ khiter_t pos;
+ git_index_entry key = {{ 0 }};
+
+ assert(index);
+
+ key.path = path;
+ GIT_IDXENTRY_STAGE_SET(&key, stage);
+
+ LOOKUP_IN_MAP(pos, index, &key);
+
+ if (git_idxmap_valid_index(index->entries_map, pos))
+ return git_idxmap_value_at(index->entries_map, pos);
+
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
+ return NULL;
+}
+
+void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st, bool trust_mode)
+{
+ entry->ctime.seconds = (int32_t)st->st_ctime;
+ entry->mtime.seconds = (int32_t)st->st_mtime;
+#if defined(GIT_USE_NSEC)
+ entry->mtime.nanoseconds = st->st_mtime_nsec;
+ entry->ctime.nanoseconds = st->st_ctime_nsec;
+#endif
+ entry->dev = st->st_rdev;
+ entry->ino = st->st_ino;
+ entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ?
+ git_index__create_mode(0666) : git_index__create_mode(st->st_mode);
+ entry->uid = st->st_uid;
+ entry->gid = st->st_gid;
+ entry->file_size = (uint32_t)st->st_size;
+}
+
+static void index_entry_adjust_namemask(
+ git_index_entry *entry,
+ size_t path_length)
+{
+ entry->flags &= ~GIT_IDXENTRY_NAMEMASK;
+
+ if (path_length < GIT_IDXENTRY_NAMEMASK)
+ entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;
+ else
+ entry->flags |= GIT_IDXENTRY_NAMEMASK;
+}
+
+/* When `from_workdir` is true, we will validate the paths to avoid placing
+ * paths that are invalid for the working directory on the current filesystem
+ * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This
+ * function will *always* prevent `.git` and directory traversal `../` from
+ * being added to the index.
+ */
+static int index_entry_create(
+ git_index_entry **out,
+ git_repository *repo,
+ const char *path,
+ bool from_workdir)
+{
+ size_t pathlen = strlen(path), alloclen;
+ struct entry_internal *entry;
+ unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS;
+
+ /* always reject placing `.git` in the index and directory traversal.
+ * when requested, disallow platform-specific filenames and upgrade to
+ * the platform-specific `.git` tests (eg, `git~1`, etc).
+ */
+ if (from_workdir)
+ path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS;
+
+ if (!git_path_isvalid(repo, path, path_valid_flags)) {
+ giterr_set(GITERR_INDEX, "invalid path: '%s'", path);
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(struct entry_internal), pathlen);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ entry = git__calloc(1, alloclen);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->pathlen = pathlen;
+ memcpy(entry->path, path, pathlen);
+ entry->entry.path = entry->path;
+
+ *out = (git_index_entry *)entry;
+ return 0;
+}
+
+static int index_entry_init(
+ git_index_entry **entry_out,
+ git_index *index,
+ const char *rel_path)
+{
+ int error = 0;
+ git_index_entry *entry = NULL;
+ struct stat st;
+ git_oid oid;
+
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not initialize index entry. "
+ "Index is not backed up by an existing repository.");
+
+ if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, true) < 0)
+ return -1;
+
+ /* write the blob to disk and get the oid and stat info */
+ error = git_blob__create_from_paths(
+ &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
+
+ if (error < 0) {
+ index_entry_free(entry);
+ return error;
+ }
+
+ entry->id = oid;
+ git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
+
+ *entry_out = (git_index_entry *)entry;
+ return 0;
+}
+
+static git_index_reuc_entry *reuc_entry_alloc(const char *path)
+{
+ size_t pathlen = strlen(path),
+ structlen = sizeof(struct reuc_entry_internal),
+ alloclen;
+ struct reuc_entry_internal *entry;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, structlen, pathlen) ||
+ GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1))
+ return NULL;
+
+ entry = git__calloc(1, alloclen);
+ if (!entry)
+ return NULL;
+
+ entry->pathlen = pathlen;
+ memcpy(entry->path, path, pathlen);
+ entry->entry.path = entry->path;
+
+ return (git_index_reuc_entry *)entry;
+}
+
+static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
+ const char *path,
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const git_oid *their_oid)
+{
+ git_index_reuc_entry *reuc = NULL;
+
+ assert(reuc_out && path);
+
+ *reuc_out = reuc = reuc_entry_alloc(path);
+ GITERR_CHECK_ALLOC(reuc);
+
+ if ((reuc->mode[0] = ancestor_mode) > 0) {
+ assert(ancestor_oid);
+ git_oid_cpy(&reuc->oid[0], ancestor_oid);
+ }
+
+ if ((reuc->mode[1] = our_mode) > 0) {
+ assert(our_oid);
+ git_oid_cpy(&reuc->oid[1], our_oid);
+ }
+
+ if ((reuc->mode[2] = their_mode) > 0) {
+ assert(their_oid);
+ git_oid_cpy(&reuc->oid[2], their_oid);
+ }
+
+ return 0;
+}
+
+static void index_entry_cpy(
+ git_index_entry *tgt,
+ const git_index_entry *src)
+{
+ const char *tgt_path = tgt->path;
+ memcpy(tgt, src, sizeof(*tgt));
+ tgt->path = tgt_path;
+}
+
+static int index_entry_dup(
+ git_index_entry **out,
+ git_index *index,
+ const git_index_entry *src)
+{
+ if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0)
+ return -1;
+
+ index_entry_cpy(*out, src);
+ return 0;
+}
+
+static void index_entry_cpy_nocache(
+ git_index_entry *tgt,
+ const git_index_entry *src)
+{
+ git_oid_cpy(&tgt->id, &src->id);
+ tgt->mode = src->mode;
+ tgt->flags = src->flags;
+ tgt->flags_extended = (src->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS);
+}
+
+static int index_entry_dup_nocache(
+ git_index_entry **out,
+ git_index *index,
+ const git_index_entry *src)
+{
+ if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0)
+ return -1;
+
+ index_entry_cpy_nocache(*out, src);
+ return 0;
+}
+
+static int has_file_name(git_index *index,
+ const git_index_entry *entry, size_t pos, int ok_to_replace)
+{
+ int retval = 0;
+ size_t len = strlen(entry->path);
+ int stage = GIT_IDXENTRY_STAGE(entry);
+ const char *name = entry->path;
+
+ while (pos < index->entries.length) {
+ struct entry_internal *p = index->entries.contents[pos++];
+
+ if (len >= p->pathlen)
+ break;
+ if (memcmp(name, p->path, len))
+ break;
+ if (GIT_IDXENTRY_STAGE(&p->entry) != stage)
+ continue;
+ if (p->path[len] != '/')
+ continue;
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+
+ if (index_remove_entry(index, --pos) < 0)
+ break;
+ }
+ return retval;
+}
+
+/*
+ * Do we have another file with a pathname that is a proper
+ * subset of the name we're trying to add?
+ */
+static int has_dir_name(git_index *index,
+ const git_index_entry *entry, int ok_to_replace)
+{
+ int retval = 0;
+ int stage = GIT_IDXENTRY_STAGE(entry);
+ const char *name = entry->path;
+ const char *slash = name + strlen(name);
+
+ for (;;) {
+ size_t len, pos;
+
+ for (;;) {
+ if (*--slash == '/')
+ break;
+ if (slash <= entry->path)
+ return retval;
+ }
+ len = slash - name;
+
+ if (!index_find(&pos, index, name, len, stage)) {
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+
+ if (index_remove_entry(index, pos) < 0)
+ break;
+ continue;
+ }
+
+ /*
+ * Trivial optimization: if we find an entry that
+ * already matches the sub-directory, then we know
+ * we're ok, and we can exit.
+ */
+ for (; pos < index->entries.length; ++pos) {
+ struct entry_internal *p = index->entries.contents[pos];
+
+ if (p->pathlen <= len ||
+ p->path[len] != '/' ||
+ memcmp(p->path, name, len))
+ break; /* not our subdirectory */
+
+ if (GIT_IDXENTRY_STAGE(&p->entry) == stage)
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+static int check_file_directory_collision(git_index *index,
+ git_index_entry *entry, size_t pos, int ok_to_replace)
+{
+ int retval = has_file_name(index, entry, pos, ok_to_replace);
+ retval = retval + has_dir_name(index, entry, ok_to_replace);
+
+ if (retval) {
+ giterr_set(GITERR_INDEX,
+ "'%s' appears as both a file and a directory", entry->path);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int canonicalize_directory_path(
+ git_index *index,
+ git_index_entry *entry,
+ git_index_entry *existing)
+{
+ const git_index_entry *match, *best = NULL;
+ char *search, *sep;
+ size_t pos, search_len, best_len;
+
+ if (!index->ignore_case)
+ return 0;
+
+ /* item already exists in the index, simply re-use the existing case */
+ if (existing) {
+ memcpy((char *)entry->path, existing->path, strlen(existing->path));
+ return 0;
+ }
+
+ /* nothing to do */
+ if (strchr(entry->path, '/') == NULL)
+ return 0;
+
+ if ((search = git__strdup(entry->path)) == NULL)
+ return -1;
+
+ /* starting at the parent directory and descending to the root, find the
+ * common parent directory.
+ */
+ while (!best && (sep = strrchr(search, '/'))) {
+ sep[1] = '\0';
+
+ search_len = strlen(search);
+
+ git_vector_bsearch2(
+ &pos, &index->entries, index->entries_search_path, search);
+
+ while ((match = git_vector_get(&index->entries, pos))) {
+ if (GIT_IDXENTRY_STAGE(match) != 0) {
+ /* conflicts do not contribute to canonical paths */
+ } else if (strncmp(search, match->path, search_len) == 0) {
+ /* prefer an exact match to the input filename */
+ best = match;
+ best_len = search_len;
+ break;
+ } else if (strncasecmp(search, match->path, search_len) == 0) {
+ /* continue walking, there may be a path with an exact
+ * (case sensitive) match later in the index, but use this
+ * as the best match until that happens.
+ */
+ if (!best) {
+ best = match;
+ best_len = search_len;
+ }
+ } else {
+ break;
+ }
+
+ pos++;
+ }
+
+ sep[0] = '\0';
+ }
+
+ if (best)
+ memcpy((char *)entry->path, best->path, best_len);
+
+ git__free(search);
+ return 0;
+}
+
+static int index_no_dups(void **old, void *new)
+{
+ const git_index_entry *entry = new;
+ GIT_UNUSED(old);
+ giterr_set(GITERR_INDEX, "'%s' appears multiple times at stage %d",
+ entry->path, GIT_IDXENTRY_STAGE(entry));
+ return GIT_EEXISTS;
+}
+
+static void index_existing_and_best(
+ git_index_entry **existing,
+ size_t *existing_position,
+ git_index_entry **best,
+ git_index *index,
+ const git_index_entry *entry)
+{
+ git_index_entry *e;
+ size_t pos;
+ int error;
+
+ error = index_find(&pos,
+ index, entry->path, 0, GIT_IDXENTRY_STAGE(entry));
+
+ if (error == 0) {
+ *existing = index->entries.contents[pos];
+ *existing_position = pos;
+ *best = index->entries.contents[pos];
+ return;
+ }
+
+ *existing = NULL;
+ *existing_position = 0;
+ *best = NULL;
+
+ if (GIT_IDXENTRY_STAGE(entry) == 0) {
+ for (; pos < index->entries.length; pos++) {
+ int (*strcomp)(const char *a, const char *b) =
+ index->ignore_case ? git__strcasecmp : git__strcmp;
+
+ e = index->entries.contents[pos];
+
+ if (strcomp(entry->path, e->path) != 0)
+ break;
+
+ if (GIT_IDXENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) {
+ *best = e;
+ continue;
+ } else {
+ *best = e;
+ break;
+ }
+ }
+ }
+}
+
+/* index_insert takes ownership of the new entry - if it can't insert
+ * it, then it will return an error **and also free the entry**. When
+ * it replaces an existing entry, it will update the entry_ptr with the
+ * actual entry in the index (and free the passed in one).
+ *
+ * trust_path is whether we use the given path, or whether (on case
+ * insensitive systems only) we try to canonicalize the given path to
+ * be within an existing directory.
+ *
+ * trust_mode is whether we trust the mode in entry_ptr.
+ *
+ * trust_id is whether we trust the id or it should be validated.
+ */
+static int index_insert(
+ git_index *index,
+ git_index_entry **entry_ptr,
+ int replace,
+ bool trust_path,
+ bool trust_mode,
+ bool trust_id)
+{
+ int error = 0;
+ size_t path_length, position;
+ git_index_entry *existing, *best, *entry;
+
+ assert(index && entry_ptr);
+
+ entry = *entry_ptr;
+
+ /* make sure that the path length flag is correct */
+ path_length = ((struct entry_internal *)entry)->pathlen;
+ index_entry_adjust_namemask(entry, path_length);
+
+ /* this entry is now up-to-date and should not be checked for raciness */
+ entry->flags_extended |= GIT_IDXENTRY_UPTODATE;
+
+ git_vector_sort(&index->entries);
+
+ /* look if an entry with this path already exists, either staged, or (if
+ * this entry is a regular staged item) as the "ours" side of a conflict.
+ */
+ index_existing_and_best(&existing, &position, &best, index, entry);
+
+ /* update the file mode */
+ entry->mode = trust_mode ?
+ git_index__create_mode(entry->mode) :
+ index_merge_mode(index, best, entry->mode);
+
+ /* canonicalize the directory name */
+ if (!trust_path)
+ error = canonicalize_directory_path(index, entry, best);
+
+ /* ensure that the given id exists (unless it's a submodule) */
+ if (!error && !trust_id && INDEX_OWNER(index) &&
+ (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) {
+
+ if (!git_object__is_valid(INDEX_OWNER(index), &entry->id,
+ git_object__type_from_filemode(entry->mode)))
+ error = -1;
+ }
+
+ /* look for tree / blob name collisions, removing conflicts if requested */
+ if (!error)
+ error = check_file_directory_collision(index, entry, position, replace);
+
+ if (error < 0)
+ /* skip changes */;
+
+ /* if we are replacing an existing item, overwrite the existing entry
+ * and return it in place of the passed in one.
+ */
+ else if (existing) {
+ if (replace) {
+ index_entry_cpy(existing, entry);
+
+ if (trust_path)
+ memcpy((char *)existing->path, entry->path, strlen(entry->path));
+ }
+
+ index_entry_free(entry);
+ *entry_ptr = entry = existing;
+ }
+ else {
+ /* if replace is not requested or no existing entry exists, insert
+ * at the sorted position. (Since we re-sort after each insert to
+ * check for dups, this is actually cheaper in the long run.)
+ */
+ error = git_vector_insert_sorted(&index->entries, entry, index_no_dups);
+
+ if (error == 0) {
+ INSERT_IN_MAP(index, entry, error);
+ }
+ }
+
+ if (error < 0) {
+ index_entry_free(*entry_ptr);
+ *entry_ptr = NULL;
+ }
+
+ return error;
+}
+
+static int index_conflict_to_reuc(git_index *index, const char *path)
+{
+ const git_index_entry *conflict_entries[3];
+ int ancestor_mode, our_mode, their_mode;
+ git_oid const *ancestor_oid, *our_oid, *their_oid;
+ int ret;
+
+ if ((ret = git_index_conflict_get(&conflict_entries[0],
+ &conflict_entries[1], &conflict_entries[2], index, path)) < 0)
+ return ret;
+
+ ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode;
+ our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode;
+ their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode;
+
+ ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id;
+ our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id;
+ their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id;
+
+ if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid,
+ our_mode, our_oid, their_mode, their_oid)) >= 0)
+ ret = git_index_conflict_remove(index, path);
+
+ return ret;
+}
+
+static bool valid_filemode(const int filemode)
+{
+ return (filemode == GIT_FILEMODE_BLOB ||
+ filemode == GIT_FILEMODE_BLOB_EXECUTABLE ||
+ filemode == GIT_FILEMODE_LINK ||
+ filemode == GIT_FILEMODE_COMMIT);
+}
+
+int git_index_add_frombuffer(
+ git_index *index, const git_index_entry *source_entry,
+ const void *buffer, size_t len)
+{
+ git_index_entry *entry = NULL;
+ int error = 0;
+ git_oid id;
+
+ assert(index && source_entry->path);
+
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not initialize index entry. "
+ "Index is not backed up by an existing repository.");
+
+ if (!valid_filemode(source_entry->mode)) {
+ giterr_set(GITERR_INDEX, "invalid filemode");
+ return -1;
+ }
+
+ if (index_entry_dup(&entry, index, source_entry) < 0)
+ return -1;
+
+ error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len);
+ if (error < 0) {
+ index_entry_free(entry);
+ return error;
+ }
+
+ git_oid_cpy(&entry->id, &id);
+ entry->file_size = len;
+
+ if ((error = index_insert(index, &entry, 1, true, true, true)) < 0)
+ return error;
+
+ /* Adding implies conflict was resolved, move conflict entries to REUC */
+ if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
+}
+
+static int add_repo_as_submodule(git_index_entry **out, git_index *index, const char *path)
+{
+ git_repository *sub;
+ git_buf abspath = GIT_BUF_INIT;
+ git_repository *repo = INDEX_OWNER(index);
+ git_reference *head;
+ git_index_entry *entry;
+ struct stat st;
+ int error;
+
+ if (index_entry_create(&entry, INDEX_OWNER(index), path, true) < 0)
+ return -1;
+
+ if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0)
+ return error;
+
+ if ((error = p_stat(abspath.ptr, &st)) < 0) {
+ giterr_set(GITERR_OS, "failed to stat repository dir");
+ return -1;
+ }
+
+ git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
+
+ if ((error = git_repository_open(&sub, abspath.ptr)) < 0)
+ return error;
+
+ if ((error = git_repository_head(&head, sub)) < 0)
+ return error;
+
+ git_oid_cpy(&entry->id, git_reference_target(head));
+ entry->mode = GIT_FILEMODE_COMMIT;
+
+ git_reference_free(head);
+ git_repository_free(sub);
+ git_buf_free(&abspath);
+
+ *out = entry;
+ return 0;
+}
+
+int git_index_add_bypath(git_index *index, const char *path)
+{
+ git_index_entry *entry = NULL;
+ int ret;
+
+ assert(index && path);
+
+ if ((ret = index_entry_init(&entry, index, path)) == 0)
+ ret = index_insert(index, &entry, 1, false, false, true);
+
+ /* If we were given a directory, let's see if it's a submodule */
+ if (ret < 0 && ret != GIT_EDIRECTORY)
+ return ret;
+
+ if (ret == GIT_EDIRECTORY) {
+ git_submodule *sm;
+ git_error_state err;
+
+ giterr_state_capture(&err, ret);
+
+ ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path);
+ if (ret == GIT_ENOTFOUND)
+ return giterr_state_restore(&err);
+
+ giterr_state_free(&err);
+
+ /*
+ * EEXISTS means that there is a repository at that path, but it's not known
+ * as a submodule. We add its HEAD as an entry and don't register it.
+ */
+ if (ret == GIT_EEXISTS) {
+ if ((ret = add_repo_as_submodule(&entry, index, path)) < 0)
+ return ret;
+
+ if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0)
+ return ret;
+ } else if (ret < 0) {
+ return ret;
+ } else {
+ ret = git_submodule_add_to_index(sm, false);
+ git_submodule_free(sm);
+ return ret;
+ }
+ }
+
+ /* Adding implies conflict was resolved, move conflict entries to REUC */
+ if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
+ return ret;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
+}
+
+int git_index_remove_bypath(git_index *index, const char *path)
+{
+ int ret;
+
+ assert(index && path);
+
+ if (((ret = git_index_remove(index, path, 0)) < 0 &&
+ ret != GIT_ENOTFOUND) ||
+ ((ret = index_conflict_to_reuc(index, path)) < 0 &&
+ ret != GIT_ENOTFOUND))
+ return ret;
+
+ if (ret == GIT_ENOTFOUND)
+ giterr_clear();
+
+ return 0;
+}
+
+int git_index__fill(git_index *index, const git_vector *source_entries)
+{
+ const git_index_entry *source_entry = NULL;
+ size_t i;
+ int ret = 0;
+
+ assert(index);
+
+ if (!source_entries->length)
+ return 0;
+
+ git_vector_size_hint(&index->entries, source_entries->length);
+ git_idxmap_resize(index->entries_map, (khint_t)(source_entries->length * 1.3));
+
+ git_vector_foreach(source_entries, i, source_entry) {
+ git_index_entry *entry = NULL;
+
+ if ((ret = index_entry_dup(&entry, index, source_entry)) < 0)
+ break;
+
+ index_entry_adjust_namemask(entry, ((struct entry_internal *)entry)->pathlen);
+ entry->flags_extended |= GIT_IDXENTRY_UPTODATE;
+ entry->mode = git_index__create_mode(entry->mode);
+
+ if ((ret = git_vector_insert(&index->entries, entry)) < 0)
+ break;
+
+ INSERT_IN_MAP(index, entry, ret);
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ git_vector_sort(&index->entries);
+
+ return ret;
+}
+
+
+int git_index_add(git_index *index, const git_index_entry *source_entry)
+{
+ git_index_entry *entry = NULL;
+ int ret;
+
+ assert(index && source_entry && source_entry->path);
+
+ if (!valid_filemode(source_entry->mode)) {
+ giterr_set(GITERR_INDEX, "invalid filemode");
+ return -1;
+ }
+
+ if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 ||
+ (ret = index_insert(index, &entry, 1, true, true, false)) < 0)
+ return ret;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
+}
+
+int git_index_remove(git_index *index, const char *path, int stage)
+{
+ int error;
+ size_t position;
+ git_index_entry remove_key = {{ 0 }};
+
+ remove_key.path = path;
+ GIT_IDXENTRY_STAGE_SET(&remove_key, stage);
+
+ DELETE_IN_MAP(index, &remove_key);
+
+ if (index_find(&position, index, path, 0, stage) < 0) {
+ giterr_set(
+ GITERR_INDEX, "Index does not contain %s at stage %d", path, stage);
+ error = GIT_ENOTFOUND;
+ } else {
+ error = index_remove_entry(index, position);
+ }
+
+ return error;
+}
+
+int git_index_remove_directory(git_index *index, const char *dir, int stage)
+{
+ git_buf pfx = GIT_BUF_INIT;
+ int error = 0;
+ size_t pos;
+ git_index_entry *entry;
+
+ if (!(error = git_buf_sets(&pfx, dir)) &&
+ !(error = git_path_to_dir(&pfx)))
+ index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY);
+
+ while (!error) {
+ entry = git_vector_get(&index->entries, pos);
+ if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)
+ break;
+
+ if (GIT_IDXENTRY_STAGE(entry) != stage) {
+ ++pos;
+ continue;
+ }
+
+ error = index_remove_entry(index, pos);
+
+ /* removed entry at 'pos' so we don't need to increment */
+ }
+
+ git_buf_free(&pfx);
+
+ return error;
+}
+
+int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix)
+{
+ int error = 0;
+ size_t pos;
+ const git_index_entry *entry;
+
+ index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY);
+ entry = git_vector_get(&index->entries, pos);
+ if (!entry || git__prefixcmp(entry->path, prefix) != 0)
+ error = GIT_ENOTFOUND;
+
+ if (!error && at_pos)
+ *at_pos = pos;
+
+ return error;
+}
+
+int git_index__find_pos(
+ size_t *out, git_index *index, const char *path, size_t path_len, int stage)
+{
+ assert(index && path);
+ return index_find(out, index, path, path_len, stage);
+}
+
+int git_index_find(size_t *at_pos, git_index *index, const char *path)
+{
+ size_t pos;
+
+ assert(index && path);
+
+ if (git_vector_bsearch2(
+ &pos, &index->entries, index->entries_search_path, path) < 0) {
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
+ return GIT_ENOTFOUND;
+ }
+
+ /* Since our binary search only looked at path, we may be in the
+ * middle of a list of stages.
+ */
+ for (; pos > 0; --pos) {
+ const git_index_entry *prev = git_vector_get(&index->entries, pos - 1);
+
+ if (index->entries_cmp_path(prev->path, path) != 0)
+ break;
+ }
+
+ if (at_pos)
+ *at_pos = pos;
+
+ return 0;
+}
+
+int git_index_conflict_add(git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry)
+{
+ git_index_entry *entries[3] = { 0 };
+ unsigned short i;
+ int ret = 0;
+
+ assert (index);
+
+ if ((ancestor_entry &&
+ (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) ||
+ (our_entry &&
+ (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) ||
+ (their_entry &&
+ (ret = index_entry_dup(&entries[2], index, their_entry)) < 0))
+ goto on_error;
+
+ /* Validate entries */
+ for (i = 0; i < 3; i++) {
+ if (entries[i] && !valid_filemode(entries[i]->mode)) {
+ giterr_set(GITERR_INDEX, "invalid filemode for stage %d entry",
+ i + 1);
+ return -1;
+ }
+ }
+
+ /* Remove existing index entries for each path */
+ for (i = 0; i < 3; i++) {
+ if (entries[i] == NULL)
+ continue;
+
+ if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) {
+ if (ret != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear();
+ ret = 0;
+ }
+ }
+
+ /* Add the conflict entries */
+ for (i = 0; i < 3; i++) {
+ if (entries[i] == NULL)
+ continue;
+
+ /* Make sure stage is correct */
+ GIT_IDXENTRY_STAGE_SET(entries[i], i + 1);
+
+ if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0)
+ goto on_error;
+
+ entries[i] = NULL; /* don't free if later entry fails */
+ }
+
+ return 0;
+
+on_error:
+ for (i = 0; i < 3; i++) {
+ if (entries[i] != NULL)
+ index_entry_free(entries[i]);
+ }
+
+ return ret;
+}
+
+static int index_conflict__get_byindex(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ size_t n)
+{
+ const git_index_entry *conflict_entry;
+ const char *path = NULL;
+ size_t count;
+ int stage, len = 0;
+
+ assert(ancestor_out && our_out && their_out && index);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ for (count = git_index_entrycount(index); n < count; ++n) {
+ conflict_entry = git_vector_get(&index->entries, n);
+
+ if (path && index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ stage = GIT_IDXENTRY_STAGE(conflict_entry);
+ path = conflict_entry->path;
+
+ switch (stage) {
+ case 3:
+ *their_out = conflict_entry;
+ len++;
+ break;
+ case 2:
+ *our_out = conflict_entry;
+ len++;
+ break;
+ case 1:
+ *ancestor_out = conflict_entry;
+ len++;
+ break;
+ default:
+ break;
+ };
+ }
+
+ return len;
+}
+
+int git_index_conflict_get(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ const char *path)
+{
+ size_t pos;
+ int len = 0;
+
+ assert(ancestor_out && our_out && their_out && index && path);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ if (git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ if ((len = index_conflict__get_byindex(
+ ancestor_out, our_out, their_out, index, pos)) < 0)
+ return len;
+ else if (len == 0)
+ return GIT_ENOTFOUND;
+
+ return 0;
+}
+
+static int index_conflict_remove(git_index *index, const char *path)
+{
+ size_t pos = 0;
+ git_index_entry *conflict_entry;
+ int error = 0;
+
+ if (path != NULL && git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) {
+
+ if (path != NULL &&
+ index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ if (GIT_IDXENTRY_STAGE(conflict_entry) == 0) {
+ pos++;
+ continue;
+ }
+
+ if ((error = index_remove_entry(index, pos)) < 0)
+ break;
+ }
+
+ return error;
+}
+
+int git_index_conflict_remove(git_index *index, const char *path)
+{
+ assert(index && path);
+ return index_conflict_remove(index, path);
+}
+
+int git_index_conflict_cleanup(git_index *index)
+{
+ assert(index);
+ return index_conflict_remove(index, NULL);
+}
+
+int git_index_has_conflicts(const git_index *index)
+{
+ size_t i;
+ git_index_entry *entry;
+
+ assert(index);
+
+ git_vector_foreach(&index->entries, i, entry) {
+ if (GIT_IDXENTRY_STAGE(entry) > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+int git_index_conflict_iterator_new(
+ git_index_conflict_iterator **iterator_out,
+ git_index *index)
+{
+ git_index_conflict_iterator *it = NULL;
+
+ assert(iterator_out && index);
+
+ it = git__calloc(1, sizeof(git_index_conflict_iterator));
+ GITERR_CHECK_ALLOC(it);
+
+ it->index = index;
+
+ *iterator_out = it;
+ return 0;
+}
+
+int git_index_conflict_next(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index_conflict_iterator *iterator)
+{
+ const git_index_entry *entry;
+ int len;
+
+ assert(ancestor_out && our_out && their_out && iterator);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ while (iterator->cur < iterator->index->entries.length) {
+ entry = git_index_get_byindex(iterator->index, iterator->cur);
+
+ if (git_index_entry_is_conflict(entry)) {
+ if ((len = index_conflict__get_byindex(
+ ancestor_out,
+ our_out,
+ their_out,
+ iterator->index,
+ iterator->cur)) < 0)
+ return len;
+
+ iterator->cur += len;
+ return 0;
+ }
+
+ iterator->cur++;
+ }
+
+ return GIT_ITEROVER;
+}
+
+void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator)
+{
+ if (iterator == NULL)
+ return;
+
+ git__free(iterator);
+}
+
+size_t git_index_name_entrycount(git_index *index)
+{
+ assert(index);
+ return index->names.length;
+}
+
+const git_index_name_entry *git_index_name_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+
+ git_vector_sort(&index->names);
+ return git_vector_get(&index->names, n);
+}
+
+static void index_name_entry_free(git_index_name_entry *ne)
+{
+ if (!ne)
+ return;
+ git__free(ne->ancestor);
+ git__free(ne->ours);
+ git__free(ne->theirs);
+ git__free(ne);
+}
+
+int git_index_name_add(git_index *index,
+ const char *ancestor, const char *ours, const char *theirs)
+{
+ git_index_name_entry *conflict_name;
+
+ assert((ancestor && ours) || (ancestor && theirs) || (ours && theirs));
+
+ conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) ||
+ (ours && !(conflict_name->ours = git__strdup(ours))) ||
+ (theirs && !(conflict_name->theirs = git__strdup(theirs))) ||
+ git_vector_insert(&index->names, conflict_name) < 0)
+ {
+ index_name_entry_free(conflict_name);
+ return -1;
+ }
+
+ return 0;
+}
+
+void git_index_name_clear(git_index *index)
+{
+ size_t i;
+ git_index_name_entry *conflict_name;
+
+ assert(index);
+
+ git_vector_foreach(&index->names, i, conflict_name)
+ index_name_entry_free(conflict_name);
+
+ git_vector_clear(&index->names);
+}
+
+size_t git_index_reuc_entrycount(git_index *index)
+{
+ assert(index);
+ return index->reuc.length;
+}
+
+static int index_reuc_on_dup(void **old, void *new)
+{
+ index_entry_reuc_free(*old);
+ *old = new;
+ return GIT_EEXISTS;
+}
+
+static int index_reuc_insert(
+ git_index *index,
+ git_index_reuc_entry *reuc)
+{
+ int res;
+
+ assert(index && reuc && reuc->path != NULL);
+ assert(git_vector_is_sorted(&index->reuc));
+
+ res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup);
+ return res == GIT_EEXISTS ? 0 : res;
+}
+
+int git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const git_oid *their_oid)
+{
+ git_index_reuc_entry *reuc = NULL;
+ int error = 0;
+
+ assert(index && path);
+
+ if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode,
+ ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 ||
+ (error = index_reuc_insert(index, reuc)) < 0)
+ index_entry_reuc_free(reuc);
+
+ return error;
+}
+
+int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path)
+{
+ return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_bypath(
+ git_index *index, const char *path)
+{
+ size_t pos;
+ assert(index && path);
+
+ if (!index->reuc.length)
+ return NULL;
+
+ assert(git_vector_is_sorted(&index->reuc));
+
+ if (git_index_reuc_find(&pos, index, path) < 0)
+ return NULL;
+
+ return git_vector_get(&index->reuc, pos);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+ assert(git_vector_is_sorted(&index->reuc));
+
+ return git_vector_get(&index->reuc, n);
+}
+
+int git_index_reuc_remove(git_index *index, size_t position)
+{
+ int error;
+ git_index_reuc_entry *reuc;
+
+ assert(git_vector_is_sorted(&index->reuc));
+
+ reuc = git_vector_get(&index->reuc, position);
+ error = git_vector_remove(&index->reuc, position);
+
+ if (!error)
+ index_entry_reuc_free(reuc);
+
+ return error;
+}
+
+void git_index_reuc_clear(git_index *index)
+{
+ size_t i;
+
+ assert(index);
+
+ for (i = 0; i < index->reuc.length; ++i)
+ index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
+
+ git_vector_clear(&index->reuc);
+}
+
+static int index_error_invalid(const char *message)
+{
+ giterr_set(GITERR_INDEX, "Invalid data in index - %s", message);
+ return -1;
+}
+
+static int read_reuc(git_index *index, const char *buffer, size_t size)
+{
+ const char *endptr;
+ size_t len;
+ int i;
+
+ /* If called multiple times, the vector might already be initialized */
+ if (index->reuc._alloc_size == 0 &&
+ git_vector_init(&index->reuc, 16, reuc_cmp) < 0)
+ return -1;
+
+ while (size) {
+ git_index_reuc_entry *lost;
+
+ len = p_strnlen(buffer, size) + 1;
+ if (size <= len)
+ return index_error_invalid("reading reuc entries");
+
+ lost = reuc_entry_alloc(buffer);
+ GITERR_CHECK_ALLOC(lost);
+
+ size -= len;
+ buffer += len;
+
+ /* read 3 ASCII octal numbers for stage entries */
+ for (i = 0; i < 3; i++) {
+ int64_t tmp;
+
+ if (git__strtol64(&tmp, buffer, &endptr, 8) < 0 ||
+ !endptr || endptr == buffer || *endptr ||
+ tmp < 0 || tmp > UINT32_MAX) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry stage");
+ }
+
+ lost->mode[i] = (uint32_t)tmp;
+
+ len = (endptr + 1) - buffer;
+ if (size <= len) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry stage");
+ }
+
+ size -= len;
+ buffer += len;
+ }
+
+ /* read up to 3 OIDs for stage entries */
+ for (i = 0; i < 3; i++) {
+ if (!lost->mode[i])
+ continue;
+ if (size < 20) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry oid");
+ }
+
+ git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
+ size -= 20;
+ buffer += 20;
+ }
+
+ /* entry was read successfully - insert into reuc vector */
+ if (git_vector_insert(&index->reuc, lost) < 0)
+ return -1;
+ }
+
+ /* entries are guaranteed to be sorted on-disk */
+ git_vector_set_sorted(&index->reuc, true);
+
+ return 0;
+}
+
+
+static int read_conflict_names(git_index *index, const char *buffer, size_t size)
+{
+ size_t len;
+
+ /* This gets called multiple times, the vector might already be initialized */
+ if (index->names._alloc_size == 0 &&
+ git_vector_init(&index->names, 16, conflict_name_cmp) < 0)
+ return -1;
+
+#define read_conflict_name(ptr) \
+ len = p_strnlen(buffer, size) + 1; \
+ if (size < len) { \
+ index_error_invalid("reading conflict name entries"); \
+ goto out_err; \
+ } \
+ if (len == 1) \
+ ptr = NULL; \
+ else { \
+ ptr = git__malloc(len); \
+ GITERR_CHECK_ALLOC(ptr); \
+ memcpy(ptr, buffer, len); \
+ } \
+ \
+ buffer += len; \
+ size -= len;
+
+ while (size) {
+ git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ read_conflict_name(conflict_name->ancestor);
+ read_conflict_name(conflict_name->ours);
+ read_conflict_name(conflict_name->theirs);
+
+ if (git_vector_insert(&index->names, conflict_name) < 0)
+ goto out_err;
+
+ continue;
+
+out_err:
+ git__free(conflict_name->ancestor);
+ git__free(conflict_name->ours);
+ git__free(conflict_name->theirs);
+ git__free(conflict_name);
+ return -1;
+ }
+
+#undef read_conflict_name
+
+ /* entries are guaranteed to be sorted on-disk */
+ git_vector_set_sorted(&index->names, true);
+
+ return 0;
+}
+
+static size_t read_entry(
+ git_index_entry **out,
+ git_index *index,
+ const void *buffer,
+ size_t buffer_size,
+ const char **last)
+{
+ size_t path_length, entry_size;
+ const char *path_ptr;
+ struct entry_short source;
+ git_index_entry entry = {{0}};
+ bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP;
+ char *tmp_path = NULL;
+
+ if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)
+ return 0;
+
+ /* buffer is not guaranteed to be aligned */
+ memcpy(&source, buffer, sizeof(struct entry_short));
+
+ entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds);
+ entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds);
+ entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds);
+ entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds);
+ entry.dev = ntohl(source.dev);
+ entry.ino = ntohl(source.ino);
+ entry.mode = ntohl(source.mode);
+ entry.uid = ntohl(source.uid);
+ entry.gid = ntohl(source.gid);
+ entry.file_size = ntohl(source.file_size);
+ git_oid_cpy(&entry.id, &source.oid);
+ entry.flags = ntohs(source.flags);
+
+ if (entry.flags & GIT_IDXENTRY_EXTENDED) {
+ uint16_t flags_raw;
+ size_t flags_offset;
+
+ flags_offset = offsetof(struct entry_long, flags_extended);
+ memcpy(&flags_raw, (const char *) buffer + flags_offset,
+ sizeof(flags_raw));
+ flags_raw = ntohs(flags_raw);
+
+ memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw));
+ path_ptr = (const char *) buffer + offsetof(struct entry_long, path);
+ } else
+ path_ptr = (const char *) buffer + offsetof(struct entry_short, path);
+
+ if (!compressed) {
+ path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;
+
+ /* if this is a very long string, we must find its
+ * real length without overflowing */
+ if (path_length == 0xFFF) {
+ const char *path_end;
+
+ path_end = memchr(path_ptr, '\0', buffer_size);
+ if (path_end == NULL)
+ return 0;
+
+ path_length = path_end - path_ptr;
+ }
+
+ if (entry.flags & GIT_IDXENTRY_EXTENDED)
+ entry_size = long_entry_size(path_length);
+ else
+ entry_size = short_entry_size(path_length);
+
+ if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
+ return 0;
+
+ entry.path = (char *)path_ptr;
+ } else {
+ size_t varint_len;
+ size_t shared = git_decode_varint((const unsigned char *)path_ptr,
+ &varint_len);
+ size_t len = strlen(path_ptr + varint_len);
+ size_t last_len = strlen(*last);
+ size_t tmp_path_len;
+
+ if (varint_len == 0)
+ return index_error_invalid("incorrect prefix length");
+
+ GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1);
+ tmp_path = git__malloc(tmp_path_len);
+ GITERR_CHECK_ALLOC(tmp_path);
+ memcpy(tmp_path, last, last_len);
+ memcpy(tmp_path + last_len, path_ptr + varint_len, len);
+ entry_size = long_entry_size(shared + len);
+ entry.path = tmp_path;
+ }
+
+ if (index_entry_dup(out, index, &entry) < 0) {
+ git__free(tmp_path);
+ return 0;
+ }
+
+ git__free(tmp_path);
+ return entry_size;
+}
+
+static int read_header(struct index_header *dest, const void *buffer)
+{
+ const struct index_header *source = buffer;
+
+ dest->signature = ntohl(source->signature);
+ if (dest->signature != INDEX_HEADER_SIG)
+ return index_error_invalid("incorrect header signature");
+
+ dest->version = ntohl(source->version);
+ if (dest->version < INDEX_VERSION_NUMBER_LB ||
+ dest->version > INDEX_VERSION_NUMBER_UB)
+ return index_error_invalid("incorrect header version");
+
+ dest->entry_count = ntohl(source->entry_count);
+ return 0;
+}
+
+static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size)
+{
+ struct index_extension dest;
+ size_t total_size;
+
+ /* buffer is not guaranteed to be aligned */
+ memcpy(&dest, buffer, sizeof(struct index_extension));
+ dest.extension_size = ntohl(dest.extension_size);
+
+ total_size = dest.extension_size + sizeof(struct index_extension);
+
+ if (dest.extension_size > total_size ||
+ buffer_size < total_size ||
+ buffer_size - total_size < INDEX_FOOTER_SIZE)
+ return 0;
+
+ /* optional extension */
+ if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') {
+ /* tree cache */
+ if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) {
+ if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size, &index->tree_pool) < 0)
+ return 0;
+ } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {
+ if (read_reuc(index, buffer + 8, dest.extension_size) < 0)
+ return 0;
+ } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) {
+ if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0)
+ return 0;
+ }
+ /* else, unsupported extension. We cannot parse this, but we can skip
+ * it by returning `total_size */
+ } else {
+ /* we cannot handle non-ignorable extensions;
+ * in fact they aren't even defined in the standard */
+ return 0;
+ }
+
+ return total_size;
+}
+
+static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
+{
+ int error = 0;
+ unsigned int i;
+ struct index_header header = { 0 };
+ git_oid checksum_calculated, checksum_expected;
+ const char **last = NULL;
+ const char *empty = "";
+
+#define seek_forward(_increase) { \
+ if (_increase >= buffer_size) { \
+ error = index_error_invalid("ran out of data while parsing"); \
+ goto done; } \
+ buffer += _increase; \
+ buffer_size -= _increase;\
+}
+
+ if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE)
+ return index_error_invalid("insufficient buffer space");
+
+ /* Precalculate the SHA1 of the files's contents -- we'll match it to
+ * the provided SHA1 in the footer */
+ git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE);
+
+ /* Parse header */
+ if ((error = read_header(&header, buffer)) < 0)
+ return error;
+
+ index->version = header.version;
+ if (index->version >= INDEX_VERSION_NUMBER_COMP)
+ last = ∅
+
+ seek_forward(INDEX_HEADER_SIZE);
+
+ assert(!index->entries.length);
+
+ if (index->ignore_case)
+ kh_resize(idxicase, (khash_t(idxicase) *) index->entries_map, header.entry_count);
+ else
+ kh_resize(idx, index->entries_map, header.entry_count);
+
+ /* Parse all the entries */
+ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {
+ git_index_entry *entry;
+ size_t entry_size = read_entry(&entry, index, buffer, buffer_size, last);
+
+ /* 0 bytes read means an object corruption */
+ if (entry_size == 0) {
+ error = index_error_invalid("invalid entry");
+ goto done;
+ }
+
+ if ((error = git_vector_insert(&index->entries, entry)) < 0) {
+ index_entry_free(entry);
+ goto done;
+ }
+
+ INSERT_IN_MAP(index, entry, error);
+
+ if (error < 0) {
+ index_entry_free(entry);
+ goto done;
+ }
+ error = 0;
+
+ seek_forward(entry_size);
+ }
+
+ if (i != header.entry_count) {
+ error = index_error_invalid("header entries changed while parsing");
+ goto done;
+ }
+
+ /* There's still space for some extensions! */
+ while (buffer_size > INDEX_FOOTER_SIZE) {
+ size_t extension_size;
+
+ extension_size = read_extension(index, buffer, buffer_size);
+
+ /* see if we have read any bytes from the extension */
+ if (extension_size == 0) {
+ error = index_error_invalid("extension is truncated");
+ goto done;
+ }
+
+ seek_forward(extension_size);
+ }
+
+ if (buffer_size != INDEX_FOOTER_SIZE) {
+ error = index_error_invalid(
+ "buffer size does not match index footer size");
+ goto done;
+ }
+
+ /* 160-bit SHA-1 over the content of the index file before this checksum. */
+ git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);
+
+ if (git_oid__cmp(&checksum_calculated, &checksum_expected) != 0) {
+ error = index_error_invalid(
+ "calculated checksum does not match expected");
+ goto done;
+ }
+
+ git_oid_cpy(&index->checksum, &checksum_calculated);
+
+#undef seek_forward
+
+ /* Entries are stored case-sensitively on disk, so re-sort now if
+ * in-memory index is supposed to be case-insensitive
+ */
+ git_vector_set_sorted(&index->entries, !index->ignore_case);
+ git_vector_sort(&index->entries);
+
+done:
+ return error;
+}
+
+static bool is_index_extended(git_index *index)
+{
+ size_t i, extended;
+ git_index_entry *entry;
+
+ extended = 0;
+
+ git_vector_foreach(&index->entries, i, entry) {
+ entry->flags &= ~GIT_IDXENTRY_EXTENDED;
+ if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) {
+ extended++;
+ entry->flags |= GIT_IDXENTRY_EXTENDED;
+ }
+ }
+
+ return (extended > 0);
+}
+
+static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last)
+{
+ void *mem = NULL;
+ struct entry_short *ondisk;
+ size_t path_len, disk_size;
+ char *path;
+ const char *path_start = entry->path;
+ size_t same_len = 0;
+
+ path_len = ((struct entry_internal *)entry)->pathlen;
+
+ if (last) {
+ const char *last_c = *last;
+
+ while (*path_start == *last_c) {
+ if (!*path_start || !*last_c)
+ break;
+ ++path_start;
+ ++last_c;
+ ++same_len;
+ }
+ path_len -= same_len;
+ *last = entry->path;
+ }
+
+ if (entry->flags & GIT_IDXENTRY_EXTENDED)
+ disk_size = long_entry_size(path_len);
+ else
+ disk_size = short_entry_size(path_len);
+
+ if (git_filebuf_reserve(file, &mem, disk_size) < 0)
+ return -1;
+
+ ondisk = (struct entry_short *)mem;
+
+ memset(ondisk, 0x0, disk_size);
+
+ /**
+ * Yes, we have to truncate.
+ *
+ * The on-disk format for Index entries clearly defines
+ * the time and size fields to be 4 bytes each -- so even if
+ * we store these values with 8 bytes on-memory, they must
+ * be truncated to 4 bytes before writing to disk.
+ *
+ * In 2038 I will be either too dead or too rich to care about this
+ */
+ ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds);
+ ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds);
+ ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds);
+ ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds);
+ ondisk->dev = htonl(entry->dev);
+ ondisk->ino = htonl(entry->ino);
+ ondisk->mode = htonl(entry->mode);
+ ondisk->uid = htonl(entry->uid);
+ ondisk->gid = htonl(entry->gid);
+ ondisk->file_size = htonl((uint32_t)entry->file_size);
+
+ git_oid_cpy(&ondisk->oid, &entry->id);
+
+ ondisk->flags = htons(entry->flags);
+
+ if (entry->flags & GIT_IDXENTRY_EXTENDED) {
+ struct entry_long *ondisk_ext;
+ ondisk_ext = (struct entry_long *)ondisk;
+ ondisk_ext->flags_extended = htons(entry->flags_extended &
+ GIT_IDXENTRY_EXTENDED_FLAGS);
+ path = ondisk_ext->path;
+ }
+ else
+ path = ondisk->path;
+
+ if (last) {
+ path += git_encode_varint((unsigned char *) path,
+ disk_size,
+ path_len - same_len);
+ }
+ memcpy(path, path_start, path_len);
+
+ return 0;
+}
+
+static int write_entries(git_index *index, git_filebuf *file)
+{
+ int error = 0;
+ size_t i;
+ git_vector case_sorted, *entries;
+ git_index_entry *entry;
+ const char **last = NULL;
+ const char *empty = "";
+
+ /* If index->entries is sorted case-insensitively, then we need
+ * to re-sort it case-sensitively before writing */
+ if (index->ignore_case) {
+ git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp);
+ git_vector_sort(&case_sorted);
+ entries = &case_sorted;
+ } else {
+ entries = &index->entries;
+ }
+
+ if (index->version >= INDEX_VERSION_NUMBER_COMP)
+ last = ∅
+
+ git_vector_foreach(entries, i, entry)
+ if ((error = write_disk_entry(file, entry, last)) < 0)
+ break;
+
+ if (index->ignore_case)
+ git_vector_free(&case_sorted);
+
+ return error;
+}
+
+static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data)
+{
+ struct index_extension ondisk;
+
+ memset(&ondisk, 0x0, sizeof(struct index_extension));
+ memcpy(&ondisk, header, 4);
+ ondisk.extension_size = htonl(header->extension_size);
+
+ git_filebuf_write(file, &ondisk, sizeof(struct index_extension));
+ return git_filebuf_write(file, data->ptr, data->size);
+}
+
+static int create_name_extension_data(git_buf *name_buf, git_index_name_entry *conflict_name)
+{
+ int error = 0;
+
+ if (conflict_name->ancestor == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->ours == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->theirs == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1);
+
+on_error:
+ return error;
+}
+
+static int write_name_extension(git_index *index, git_filebuf *file)
+{
+ git_buf name_buf = GIT_BUF_INIT;
+ git_vector *out = &index->names;
+ git_index_name_entry *conflict_name;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, conflict_name) {
+ if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4);
+ extension.extension_size = (uint32_t)name_buf.size;
+
+ error = write_extension(file, &extension, &name_buf);
+
+ git_buf_free(&name_buf);
+
+done:
+ return error;
+}
+
+static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)
+{
+ int i;
+ int error = 0;
+
+ if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0)
+ return error;
+
+ for (i = 0; i < 3; i++) {
+ if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 ||
+ (error = git_buf_put(reuc_buf, "\0", 1)) < 0)
+ return error;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int write_reuc_extension(git_index *index, git_filebuf *file)
+{
+ git_buf reuc_buf = GIT_BUF_INIT;
+ git_vector *out = &index->reuc;
+ git_index_reuc_entry *reuc;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, reuc) {
+ if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4);
+ extension.extension_size = (uint32_t)reuc_buf.size;
+
+ error = write_extension(file, &extension, &reuc_buf);
+
+ git_buf_free(&reuc_buf);
+
+done:
+ return error;
+}
+
+static int write_tree_extension(git_index *index, git_filebuf *file)
+{
+ struct index_extension extension;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (index->tree == NULL)
+ return 0;
+
+ if ((error = git_tree_cache_write(&buf, index->tree)) < 0)
+ return error;
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_TREECACHE_SIG, 4);
+ extension.extension_size = (uint32_t)buf.size;
+
+ error = write_extension(file, &extension, &buf);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static void clear_uptodate(git_index *index)
+{
+ git_index_entry *entry;
+ size_t i;
+
+ git_vector_foreach(&index->entries, i, entry)
+ entry->flags_extended &= ~GIT_IDXENTRY_UPTODATE;
+}
+
+static int write_index(git_oid *checksum, git_index *index, git_filebuf *file)
+{
+ git_oid hash_final;
+ struct index_header header;
+ bool is_extended;
+ uint32_t index_version_number;
+
+ assert(index && file);
+
+ if (index->version <= INDEX_VERSION_NUMBER_EXT) {
+ is_extended = is_index_extended(index);
+ index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB;
+ } else {
+ index_version_number = index->version;
+ }
+
+ header.signature = htonl(INDEX_HEADER_SIG);
+ header.version = htonl(index_version_number);
+ header.entry_count = htonl((uint32_t)index->entries.length);
+
+ if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0)
+ return -1;
+
+ if (write_entries(index, file) < 0)
+ return -1;
+
+ /* write the tree cache extension */
+ if (index->tree != NULL && write_tree_extension(index, file) < 0)
+ return -1;
+
+ /* write the rename conflict extension */
+ if (index->names.length > 0 && write_name_extension(index, file) < 0)
+ return -1;
+
+ /* write the reuc extension */
+ if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)
+ return -1;
+
+ /* get out the hash for all the contents we've appended to the file */
+ git_filebuf_hash(&hash_final, file);
+ git_oid_cpy(checksum, &hash_final);
+
+ /* write it at the end of the file */
+ if (git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ) < 0)
+ return -1;
+
+ /* file entries are no longer up to date */
+ clear_uptodate(index);
+
+ return 0;
+}
+
+int git_index_entry_stage(const git_index_entry *entry)
+{
+ return GIT_IDXENTRY_STAGE(entry);
+}
+
+int git_index_entry_is_conflict(const git_index_entry *entry)
+{
+ return (GIT_IDXENTRY_STAGE(entry) > 0);
+}
+
+typedef struct read_tree_data {
+ git_index *index;
+ git_vector *old_entries;
+ git_vector *new_entries;
+ git_vector_cmp entry_cmp;
+ git_tree_cache *tree;
+} read_tree_data;
+
+static int read_tree_cb(
+ const char *root, const git_tree_entry *tentry, void *payload)
+{
+ read_tree_data *data = payload;
+ git_index_entry *entry = NULL, *old_entry;
+ git_buf path = GIT_BUF_INIT;
+ size_t pos;
+
+ if (git_tree_entry__is_tree(tentry))
+ return 0;
+
+ if (git_buf_joinpath(&path, root, tentry->filename) < 0)
+ return -1;
+
+ if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, false) < 0)
+ return -1;
+
+ entry->mode = tentry->attr;
+ git_oid_cpy(&entry->id, git_tree_entry_id(tentry));
+
+ /* look for corresponding old entry and copy data to new entry */
+ if (data->old_entries != NULL &&
+ !index_find_in_entries(
+ &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) &&
+ (old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
+ entry->mode == old_entry->mode &&
+ git_oid_equal(&entry->id, &old_entry->id))
+ {
+ index_entry_cpy(entry, old_entry);
+ entry->flags_extended = 0;
+ }
+
+ index_entry_adjust_namemask(entry, path.size);
+ git_buf_free(&path);
+
+ if (git_vector_insert(data->new_entries, entry) < 0) {
+ index_entry_free(entry);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_index_read_tree(git_index *index, const git_tree *tree)
+{
+ int error = 0;
+ git_vector entries = GIT_VECTOR_INIT;
+ git_idxmap *entries_map;
+ read_tree_data data;
+ size_t i;
+ git_index_entry *e;
+
+ if (git_idxmap_alloc(&entries_map) < 0)
+ return -1;
+
+ git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */
+
+ data.index = index;
+ data.old_entries = &index->entries;
+ data.new_entries = &entries;
+ data.entry_cmp = index->entries_search;
+
+ index->tree = NULL;
+ git_pool_clear(&index->tree_pool);
+
+ git_vector_sort(&index->entries);
+
+ if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0)
+ goto cleanup;
+
+ if (index->ignore_case)
+ kh_resize(idxicase, (khash_t(idxicase) *) entries_map, entries.length);
+ else
+ kh_resize(idx, entries_map, entries.length);
+
+ git_vector_foreach(&entries, i, e) {
+ INSERT_IN_MAP_EX(index, entries_map, e, error);
+
+ if (error < 0) {
+ giterr_set(GITERR_INDEX, "failed to insert entry into map");
+ return error;
+ }
+ }
+
+ error = 0;
+
+ git_vector_sort(&entries);
+
+ if ((error = git_index_clear(index)) < 0) {
+ /* well, this isn't good */;
+ } else {
+ git_vector_swap(&entries, &index->entries);
+ entries_map = git__swap(index->entries_map, entries_map);
+ }
+
+cleanup:
+ git_vector_free(&entries);
+ git_idxmap_free(entries_map);
+ if (error < 0)
+ return error;
+
+ error = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool);
+
+ return error;
+}
+
+static int git_index_read_iterator(
+ git_index *index,
+ git_iterator *new_iterator,
+ size_t new_length_hint)
+{
+ git_vector new_entries = GIT_VECTOR_INIT,
+ remove_entries = GIT_VECTOR_INIT;
+ git_idxmap *new_entries_map = NULL;
+ git_iterator *index_iterator = NULL;
+ git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *old_entry, *new_entry;
+ git_index_entry *entry;
+ size_t i;
+ int error;
+
+ assert((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE));
+
+ if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 ||
+ (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 ||
+ (error = git_idxmap_alloc(&new_entries_map)) < 0)
+ goto done;
+
+ if (index->ignore_case && new_length_hint)
+ kh_resize(idxicase, (khash_t(idxicase) *) new_entries_map, new_length_hint);
+ else if (new_length_hint)
+ kh_resize(idx, new_entries_map, new_length_hint);
+
+ opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_CONFLICTS;
+
+ if ((error = git_iterator_for_index(&index_iterator,
+ git_index_owner(index), index, &opts)) < 0 ||
+ ((error = git_iterator_current(&old_entry, index_iterator)) < 0 &&
+ error != GIT_ITEROVER) ||
+ ((error = git_iterator_current(&new_entry, new_iterator)) < 0 &&
+ error != GIT_ITEROVER))
+ goto done;
+
+ while (true) {
+ git_index_entry
+ *dup_entry = NULL,
+ *add_entry = NULL,
+ *remove_entry = NULL;
+ int diff;
+
+ error = 0;
+
+ if (old_entry && new_entry)
+ diff = git_index_entry_cmp(old_entry, new_entry);
+ else if (!old_entry && new_entry)
+ diff = 1;
+ else if (old_entry && !new_entry)
+ diff = -1;
+ else
+ break;
+
+ if (diff < 0) {
+ remove_entry = (git_index_entry *)old_entry;
+ } else if (diff > 0) {
+ dup_entry = (git_index_entry *)new_entry;
+ } else {
+ /* Path and stage are equal, if the OID is equal, keep it to
+ * keep the stat cache data.
+ */
+ if (git_oid_equal(&old_entry->id, &new_entry->id) &&
+ old_entry->mode == new_entry->mode) {
+ add_entry = (git_index_entry *)old_entry;
+ } else {
+ dup_entry = (git_index_entry *)new_entry;
+ remove_entry = (git_index_entry *)old_entry;
+ }
+ }
+
+ if (dup_entry) {
+ if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0)
+ goto done;
+
+ index_entry_adjust_namemask(add_entry,
+ ((struct entry_internal *)add_entry)->pathlen);
+ }
+
+ /* invalidate this path in the tree cache if this is new (to
+ * invalidate the parent trees)
+ */
+ if (dup_entry && !remove_entry && index->tree)
+ git_tree_cache_invalidate_path(index->tree, dup_entry->path);
+
+ if (add_entry) {
+ if ((error = git_vector_insert(&new_entries, add_entry)) == 0)
+ INSERT_IN_MAP_EX(index, new_entries_map, add_entry, error);
+ }
+
+ if (remove_entry && error >= 0)
+ error = git_vector_insert(&remove_entries, remove_entry);
+
+ if (error < 0) {
+ giterr_set(GITERR_INDEX, "failed to insert entry");
+ goto done;
+ }
+
+ if (diff <= 0) {
+ if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 &&
+ error != GIT_ITEROVER)
+ goto done;
+ }
+
+ if (diff >= 0) {
+ if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 &&
+ error != GIT_ITEROVER)
+ goto done;
+ }
+ }
+
+ git_index_name_clear(index);
+ git_index_reuc_clear(index);
+
+ git_vector_swap(&new_entries, &index->entries);
+ new_entries_map = git__swap(index->entries_map, new_entries_map);
+
+ git_vector_foreach(&remove_entries, i, entry) {
+ if (index->tree)
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+
+ index_entry_free(entry);
+ }
+
+ clear_uptodate(index);
+
+ error = 0;
+
+done:
+ git_idxmap_free(new_entries_map);
+ git_vector_free(&new_entries);
+ git_vector_free(&remove_entries);
+ git_iterator_free(index_iterator);
+ return error;
+}
+
+int git_index_read_index(
+ git_index *index,
+ const git_index *new_index)
+{
+ git_iterator *new_iterator = NULL;
+ git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error;
+
+ opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_CONFLICTS;
+
+ if ((error = git_iterator_for_index(&new_iterator,
+ git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 ||
+ (error = git_index_read_iterator(index, new_iterator,
+ new_index->entries.length)) < 0)
+ goto done;
+
+done:
+ git_iterator_free(new_iterator);
+ return error;
+}
+
+git_repository *git_index_owner(const git_index *index)
+{
+ return INDEX_OWNER(index);
+}
+
+enum {
+ INDEX_ACTION_NONE = 0,
+ INDEX_ACTION_UPDATE = 1,
+ INDEX_ACTION_REMOVE = 2,
+ INDEX_ACTION_ADDALL = 3,
+};
+
+int git_index_add_all(
+ git_index *index,
+ const git_strarray *paths,
+ unsigned int flags,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error;
+ git_repository *repo;
+ git_iterator *wditer = NULL;
+ git_pathspec ps;
+ bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0;
+
+ assert(index);
+
+ repo = INDEX_OWNER(index);
+ if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0)
+ return error;
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ /* optionally check that pathspec doesn't mention any ignored files */
+ if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 &&
+ (flags & GIT_INDEX_ADD_FORCE) == 0 &&
+ (error = git_ignore__check_pathspec_for_exact_ignores(
+ repo, &ps.pathspec, no_fnmatch)) < 0)
+ goto cleanup;
+
+ error = index_apply_to_wd_diff(index, INDEX_ACTION_ADDALL, paths, flags, cb, payload);
+
+ if (error)
+ giterr_set_after_callback(error);
+
+cleanup:
+ git_iterator_free(wditer);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+struct foreach_diff_data {
+ git_index *index;
+ const git_pathspec *pathspec;
+ unsigned int flags;
+ git_index_matched_path_cb cb;
+ void *payload;
+};
+
+static int apply_each_file(const git_diff_delta *delta, float progress, void *payload)
+{
+ struct foreach_diff_data *data = payload;
+ const char *match, *path;
+ int error = 0;
+
+ GIT_UNUSED(progress);
+
+ path = delta->old_file.path;
+
+ /* We only want those which match the pathspecs */
+ if (!git_pathspec__match(
+ &data->pathspec->pathspec, path, false, (bool)data->index->ignore_case,
+ &match, NULL))
+ return 0;
+
+ if (data->cb)
+ error = data->cb(path, match, data->payload);
+
+ if (error > 0) /* skip this entry */
+ return 0;
+ if (error < 0) /* actual error */
+ return error;
+
+ /* If the workdir item does not exist, remove it from the index. */
+ if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0)
+ error = git_index_remove_bypath(data->index, path);
+ else
+ error = git_index_add_bypath(data->index, delta->new_file.path);
+
+ return error;
+}
+
+static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths,
+ unsigned int flags,
+ git_index_matched_path_cb cb, void *payload)
+{
+ int error;
+ git_diff *diff;
+ git_pathspec ps;
+ git_repository *repo;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct foreach_diff_data data = {
+ index,
+ NULL,
+ flags,
+ cb,
+ payload,
+ };
+
+ assert(index);
+ assert(action == INDEX_ACTION_UPDATE || action == INDEX_ACTION_ADDALL);
+
+ repo = INDEX_OWNER(index);
+
+ if (!repo) {
+ return create_index_error(-1,
+ "cannot run update; the index is not backed up by a repository.");
+ }
+
+ /*
+ * We do the matching ourselves intead of passing the list to
+ * diff because we want to tell the callback which one
+ * matched, which we do not know if we ask diff to filter for us.
+ */
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
+ if (action == INDEX_ACTION_ADDALL) {
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+
+ if (flags == GIT_INDEX_ADD_FORCE)
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ }
+
+ if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0)
+ goto cleanup;
+
+ data.pathspec = &ps;
+ error = git_diff_foreach(diff, apply_each_file, NULL, NULL, NULL, &data);
+ git_diff_free(diff);
+
+ if (error) /* make sure error is set if callback stopped iteration */
+ giterr_set_after_callback(error);
+
+cleanup:
+ git_pathspec__clear(&ps);
+ return error;
+}
+
+static int index_apply_to_all(
+ git_index *index,
+ int action,
+ const git_strarray *paths,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = 0;
+ size_t i;
+ git_pathspec ps;
+ const char *match;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(index);
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ git_vector_sort(&index->entries);
+
+ for (i = 0; !error && i < index->entries.length; ++i) {
+ git_index_entry *entry = git_vector_get(&index->entries, i);
+
+ /* check if path actually matches */
+ if (!git_pathspec__match(
+ &ps.pathspec, entry->path, false, (bool)index->ignore_case,
+ &match, NULL))
+ continue;
+
+ /* issue notification callback if requested */
+ if (cb && (error = cb(entry->path, match, payload)) != 0) {
+ if (error > 0) { /* return > 0 means skip this one */
+ error = 0;
+ continue;
+ }
+ if (error < 0) /* return < 0 means abort */
+ break;
+ }
+
+ /* index manipulation may alter entry, so don't depend on it */
+ if ((error = git_buf_sets(&path, entry->path)) < 0)
+ break;
+
+ switch (action) {
+ case INDEX_ACTION_NONE:
+ break;
+ case INDEX_ACTION_UPDATE:
+ error = git_index_add_bypath(index, path.ptr);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+
+ error = git_index_remove_bypath(index, path.ptr);
+
+ if (!error) /* back up foreach if we removed this */
+ i--;
+ }
+ break;
+ case INDEX_ACTION_REMOVE:
+ if (!(error = git_index_remove_bypath(index, path.ptr)))
+ i--; /* back up foreach if we removed this */
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown index action %d", action);
+ error = -1;
+ break;
+ }
+ }
+
+ git_buf_free(&path);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+int git_index_remove_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = index_apply_to_all(
+ index, INDEX_ACTION_REMOVE, pathspec, cb, payload);
+
+ if (error) /* make sure error is set if callback stopped iteration */
+ giterr_set_after_callback(error);
+
+ return error;
+}
+
+int git_index_update_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = index_apply_to_wd_diff(index, INDEX_ACTION_UPDATE, pathspec, 0, cb, payload);
+ if (error) /* make sure error is set if callback stopped iteration */
+ giterr_set_after_callback(error);
+
+ return error;
+}
+
+int git_index_snapshot_new(git_vector *snap, git_index *index)
+{
+ int error;
+
+ GIT_REFCOUNT_INC(index);
+
+ git_atomic_inc(&index->readers);
+ git_vector_sort(&index->entries);
+
+ error = git_vector_dup(snap, &index->entries, index->entries._cmp);
+
+ if (error < 0)
+ git_index_free(index);
+
+ return error;
+}
+
+void git_index_snapshot_release(git_vector *snap, git_index *index)
+{
+ git_vector_free(snap);
+
+ git_atomic_dec(&index->readers);
+
+ git_index_free(index);
+}
+
+int git_index_snapshot_find(
+ size_t *out, git_vector *entries, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage)
+{
+ return index_find_in_entries(out, entries, entry_srch, path, path_len, stage);
+}
+
+int git_indexwriter_init(
+ git_indexwriter *writer,
+ git_index *index)
+{
+ int error;
+
+ GIT_REFCOUNT_INC(index);
+
+ writer->index = index;
+
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to write index: The index is in-memory only");
+
+ if ((error = git_filebuf_open(
+ &writer->file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) {
+
+ if (error == GIT_ELOCKED)
+ giterr_set(GITERR_INDEX, "The index is locked. This might be due to a concurrent or crashed process");
+
+ return error;
+ }
+
+ writer->should_write = 1;
+
+ return 0;
+}
+
+int git_indexwriter_init_for_operation(
+ git_indexwriter *writer,
+ git_repository *repo,
+ unsigned int *checkout_strategy)
+{
+ git_index *index;
+ int error;
+
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
+ (error = git_indexwriter_init(writer, index)) < 0)
+ return error;
+
+ writer->should_write = (*checkout_strategy & GIT_CHECKOUT_DONT_WRITE_INDEX) == 0;
+ *checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX;
+
+ return 0;
+}
+
+int git_indexwriter_commit(git_indexwriter *writer)
+{
+ int error;
+ git_oid checksum = {{ 0 }};
+
+ if (!writer->should_write)
+ return 0;
+
+ git_vector_sort(&writer->index->entries);
+ git_vector_sort(&writer->index->reuc);
+
+ if ((error = write_index(&checksum, writer->index, &writer->file)) < 0) {
+ git_indexwriter_cleanup(writer);
+ return error;
+ }
+
+ if ((error = git_filebuf_commit(&writer->file)) < 0)
+ return error;
+
+ if ((error = git_futils_filestamp_check(
+ &writer->index->stamp, writer->index->index_file_path)) < 0) {
+ giterr_set(GITERR_OS, "Could not read index timestamp");
+ return -1;
+ }
+
+ writer->index->on_disk = 1;
+ git_oid_cpy(&writer->index->checksum, &checksum);
+
+ git_index_free(writer->index);
+ writer->index = NULL;
+
+ return 0;
+}
+
+void git_indexwriter_cleanup(git_indexwriter *writer)
+{
+ git_filebuf_cleanup(&writer->file);
+
+ git_index_free(writer->index);
+ writer->index = NULL;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_index_h__
+#define INCLUDE_index_h__
+
+#include "fileops.h"
+#include "filebuf.h"
+#include "vector.h"
+#include "idxmap.h"
+#include "tree-cache.h"
+#include "git2/odb.h"
+#include "git2/index.h"
+
+#define GIT_INDEX_FILE "index"
+#define GIT_INDEX_FILE_MODE 0666
+
+struct git_index {
+ git_refcount rc;
+
+ char *index_file_path;
+ git_futils_filestamp stamp;
+ git_oid checksum; /* checksum at the end of the file */
+
+ git_vector entries;
+ git_idxmap *entries_map;
+
+ git_vector deleted; /* deleted entries if readers > 0 */
+ git_atomic readers; /* number of active iterators */
+
+ unsigned int on_disk:1;
+ unsigned int ignore_case:1;
+ unsigned int distrust_filemode:1;
+ unsigned int no_symlinks:1;
+
+ git_tree_cache *tree;
+ git_pool tree_pool;
+
+ git_vector names;
+ git_vector reuc;
+
+ git_vector_cmp entries_cmp_path;
+ git_vector_cmp entries_search;
+ git_vector_cmp entries_search_path;
+ git_vector_cmp reuc_search;
+
+ unsigned int version;
+};
+
+struct git_index_conflict_iterator {
+ git_index *index;
+ size_t cur;
+};
+
+extern void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st, bool trust_mode);
+
+/* Index entry comparison functions for array sorting */
+extern int git_index_entry_cmp(const void *a, const void *b);
+extern int git_index_entry_icmp(const void *a, const void *b);
+
+/* Index entry search functions for search using a search spec */
+extern int git_index_entry_srch(const void *a, const void *b);
+extern int git_index_entry_isrch(const void *a, const void *b);
+
+/* Index time handling functions */
+GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two)
+{
+ if (one->seconds != two->seconds)
+ return false;
+
+#ifdef GIT_USE_NSEC
+ if (one->nanoseconds != two->nanoseconds)
+ return false;
+#endif
+
+ return true;
+}
+
+/*
+ * Test if the given index time is newer than the given existing index entry.
+ * If the timestamps are exactly equivalent, then the given index time is
+ * considered "racily newer" than the existing index entry.
+ */
+GIT_INLINE(bool) git_index_entry_newer_than_index(
+ const git_index_entry *entry, git_index *index)
+{
+ /* If we never read the index, we can't have this race either */
+ if (!index || index->stamp.mtime.tv_sec == 0)
+ return false;
+
+ /* If the timestamp is the same or newer than the index, it's racy */
+#if defined(GIT_USE_NSEC)
+ if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds)
+ return true;
+ else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds)
+ return false;
+ else
+ return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds;
+#else
+ return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds;
+#endif
+}
+
+/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist
+ * (but not setting an error message).
+ *
+ * `at_pos` is set to the position where it is or would be inserted.
+ * Pass `path_len` as strlen of path or 0 to call strlen internally.
+ */
+extern int git_index__find_pos(
+ size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage);
+
+extern int git_index__fill(git_index *index, const git_vector *source_entries);
+
+extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
+
+extern unsigned int git_index__create_mode(unsigned int mode);
+
+GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index)
+{
+ return &index->stamp;
+}
+
+extern int git_index__changed_relative_to(git_index *index, const git_oid *checksum);
+
+/* Copy the current entries vector *and* increment the index refcount.
+ * Call `git_index__release_snapshot` when done.
+ */
+extern int git_index_snapshot_new(git_vector *snap, git_index *index);
+extern void git_index_snapshot_release(git_vector *snap, git_index *index);
+
+/* Allow searching in a snapshot; entries must already be sorted! */
+extern int git_index_snapshot_find(
+ size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage);
+
+/* Replace an index with a new index */
+int git_index_read_index(git_index *index, const git_index *new_index);
+
+typedef struct {
+ git_index *index;
+ git_filebuf file;
+ unsigned int should_write:1;
+} git_indexwriter;
+
+#define GIT_INDEXWRITER_INIT { NULL, GIT_FILEBUF_INIT }
+
+/* Lock the index for eventual writing. */
+extern int git_indexwriter_init(git_indexwriter *writer, git_index *index);
+
+/* Lock the index for eventual writing by a repository operation: a merge,
+ * revert, cherry-pick or a rebase. Note that the given checkout strategy
+ * will be updated for the operation's use so that checkout will not write
+ * the index.
+ */
+extern int git_indexwriter_init_for_operation(
+ git_indexwriter *writer,
+ git_repository *repo,
+ unsigned int *checkout_strategy);
+
+/* Write the index and unlock it. */
+extern int git_indexwriter_commit(git_indexwriter *writer);
+
+/* Cleanup an index writing session, unlocking the file (if it is still
+ * locked and freeing any data structures.
+ */
+extern void git_indexwriter_cleanup(git_indexwriter *writer);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/indexer.h"
+#include "git2/object.h"
+
+#include "common.h"
+#include "pack.h"
+#include "mwindow.h"
+#include "posix.h"
+#include "pack.h"
+#include "filebuf.h"
+#include "oid.h"
+#include "oidmap.h"
+#include "zstream.h"
+
+GIT__USE_OIDMAP
+
+extern git_mutex git__mwindow_mutex;
+
+#define UINT31_MAX (0x7FFFFFFF)
+
+struct entry {
+ git_oid oid;
+ uint32_t crc;
+ uint32_t offset;
+ uint64_t offset_long;
+};
+
+struct git_indexer {
+ unsigned int parsed_header :1,
+ opened_pack :1,
+ have_stream :1,
+ have_delta :1;
+ struct git_pack_header hdr;
+ struct git_pack_file *pack;
+ unsigned int mode;
+ git_off_t off;
+ git_off_t entry_start;
+ git_packfile_stream stream;
+ size_t nr_objects;
+ git_vector objects;
+ git_vector deltas;
+ unsigned int fanout[256];
+ git_hash_ctx hash_ctx;
+ git_oid hash;
+ git_transfer_progress_cb progress_cb;
+ void *progress_payload;
+ char objbuf[8*1024];
+
+ /* Needed to look up objects which we want to inject to fix a thin pack */
+ git_odb *odb;
+
+ /* Fields for calculating the packfile trailer (hash of everything before it) */
+ char inbuf[GIT_OID_RAWSZ];
+ size_t inbuf_len;
+ git_hash_ctx trailer;
+};
+
+struct delta_info {
+ git_off_t delta_off;
+};
+
+const git_oid *git_indexer_hash(const git_indexer *idx)
+{
+ return &idx->hash;
+}
+
+static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack)
+{
+ int error;
+ git_map map;
+
+ if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0)
+ return error;
+
+ memcpy(hdr, map.data, sizeof(*hdr));
+ p_munmap(&map);
+
+ /* Verify we recognize this pack file format. */
+ if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) {
+ giterr_set(GITERR_INDEXER, "Wrong pack signature");
+ return -1;
+ }
+
+ if (!pack_version_ok(hdr->hdr_version)) {
+ giterr_set(GITERR_INDEXER, "Wrong pack version");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int objects_cmp(const void *a, const void *b)
+{
+ const struct entry *entrya = a;
+ const struct entry *entryb = b;
+
+ return git_oid__cmp(&entrya->oid, &entryb->oid);
+}
+
+int git_indexer_new(
+ git_indexer **out,
+ const char *prefix,
+ unsigned int mode,
+ git_odb *odb,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload)
+{
+ git_indexer *idx;
+ git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT;
+ static const char suff[] = "/pack";
+ int error, fd = -1;
+
+ idx = git__calloc(1, sizeof(git_indexer));
+ GITERR_CHECK_ALLOC(idx);
+ idx->odb = odb;
+ idx->progress_cb = progress_cb;
+ idx->progress_payload = progress_payload;
+ idx->mode = mode ? mode : GIT_PACK_FILE_MODE;
+ git_hash_ctx_init(&idx->hash_ctx);
+ git_hash_ctx_init(&idx->trailer);
+
+ error = git_buf_joinpath(&path, prefix, suff);
+ if (error < 0)
+ goto cleanup;
+
+ fd = git_futils_mktmp(&tmp_path, git_buf_cstr(&path), idx->mode);
+ git_buf_free(&path);
+ if (fd < 0)
+ goto cleanup;
+
+ error = git_packfile_alloc(&idx->pack, git_buf_cstr(&tmp_path));
+ git_buf_free(&tmp_path);
+
+ if (error < 0)
+ goto cleanup;
+
+ idx->pack->mwf.fd = fd;
+ if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0)
+ goto cleanup;
+
+ *out = idx;
+ return 0;
+
+cleanup:
+ if (fd != -1)
+ p_close(fd);
+
+ git_buf_free(&path);
+ git_buf_free(&tmp_path);
+ git__free(idx);
+ return -1;
+}
+
+/* Try to store the delta so we can try to resolve it later */
+static int store_delta(git_indexer *idx)
+{
+ struct delta_info *delta;
+
+ delta = git__calloc(1, sizeof(struct delta_info));
+ GITERR_CHECK_ALLOC(delta);
+ delta->delta_off = idx->entry_start;
+
+ if (git_vector_insert(&idx->deltas, delta) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type)
+{
+ char buffer[64];
+ size_t hdrlen;
+
+ hdrlen = git_odb__format_object_header(buffer, sizeof(buffer), (size_t)len, type);
+ git_hash_update(ctx, buffer, hdrlen);
+}
+
+static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(idx && stream);
+
+ do {
+ if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0)
+ break;
+
+ git_hash_update(&idx->hash_ctx, idx->objbuf, read);
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+/* In order to create the packfile stream, we need to skip over the delta base description */
+static int advance_delta_offset(git_indexer *idx, git_otype type)
+{
+ git_mwindow *w = NULL;
+
+ assert(type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA);
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ idx->off += GIT_OID_RAWSZ;
+ } else {
+ git_off_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start);
+ git_mwindow_close(&w);
+ if (base_off < 0)
+ return (int)base_off;
+ }
+
+ return 0;
+}
+
+/* Read from the stream and discard any output */
+static int read_object_stream(git_indexer *idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(stream);
+
+ do {
+ read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf));
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, git_off_t size)
+{
+ void *ptr;
+ uint32_t crc;
+ unsigned int left, len;
+ git_mwindow *w = NULL;
+
+ crc = crc32(0L, Z_NULL, 0);
+ while (size) {
+ ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left);
+ if (ptr == NULL)
+ return -1;
+
+ len = min(left, (unsigned int)size);
+ crc = crc32(crc, ptr, len);
+ size -= len;
+ start += len;
+ git_mwindow_close(&w);
+ }
+
+ *crc_out = htonl(crc);
+ return 0;
+}
+
+static int store_object(git_indexer *idx)
+{
+ int i, error;
+ khiter_t k;
+ git_oid oid;
+ struct entry *entry;
+ git_off_t entry_size;
+ struct git_pack_entry *pentry;
+ git_off_t entry_start = idx->entry_start;
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_hash_final(&oid, &idx->hash_ctx);
+ entry_size = idx->off - entry_start;
+ if (entry_start > UINT31_MAX) {
+ entry->offset = UINT32_MAX;
+ entry->offset_long = entry_start;
+ } else {
+ entry->offset = (uint32_t)entry_start;
+ }
+
+ git_oid_cpy(&pentry->sha1, &oid);
+ pentry->offset = entry_start;
+
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+ if (error == -1) {
+ git__free(pentry);
+ giterr_set_oom();
+ goto on_error;
+ }
+
+ if (error == 0) {
+ giterr_set(GITERR_INDEXER, "duplicate object %s found in pack", git_oid_tostr_s(&pentry->sha1));
+ git__free(pentry);
+ goto on_error;
+ }
+
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
+
+ git_oid_cpy(&entry->oid, &oid);
+
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
+ goto on_error;
+
+ /* Add the object to the list */
+ if (git_vector_insert(&idx->objects, entry) < 0)
+ goto on_error;
+
+ for (i = oid.id[0]; i < 256; ++i) {
+ idx->fanout[i]++;
+ }
+
+ return 0;
+
+on_error:
+ git__free(entry);
+
+ return -1;
+}
+
+GIT_INLINE(bool) has_entry(git_indexer *idx, git_oid *id)
+{
+ khiter_t k;
+ k = kh_get(oid, idx->pack->idx_cache, id);
+ return (k != kh_end(idx->pack->idx_cache));
+}
+
+static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, git_off_t entry_start)
+{
+ int i, error;
+ khiter_t k;
+
+ if (entry_start > UINT31_MAX) {
+ entry->offset = UINT32_MAX;
+ entry->offset_long = entry_start;
+ } else {
+ entry->offset = (uint32_t)entry_start;
+ }
+
+ pentry->offset = entry_start;
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+
+ if (error <= 0) {
+ giterr_set(GITERR_INDEXER, "cannot insert object into pack");
+ return -1;
+ }
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
+
+ /* Add the object to the list */
+ if (git_vector_insert(&idx->objects, entry) < 0)
+ return -1;
+
+ for (i = entry->oid.id[0]; i < 256; ++i) {
+ idx->fanout[i]++;
+ }
+
+ return 0;
+}
+
+static int hash_and_save(git_indexer *idx, git_rawobj *obj, git_off_t entry_start)
+{
+ git_oid oid;
+ size_t entry_size;
+ struct entry *entry;
+ struct git_pack_entry *pentry = NULL;
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if (git_odb__hashobj(&oid, obj) < 0) {
+ giterr_set(GITERR_INDEXER, "Failed to hash object");
+ goto on_error;
+ }
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_oid_cpy(&pentry->sha1, &oid);
+ git_oid_cpy(&entry->oid, &oid);
+ entry->crc = crc32(0L, Z_NULL, 0);
+
+ entry_size = (size_t)(idx->off - entry_start);
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
+ goto on_error;
+
+ return save_entry(idx, entry, pentry, entry_start);
+
+on_error:
+ git__free(pentry);
+ git__free(entry);
+ git__free(obj->data);
+ return -1;
+}
+
+static int do_progress_callback(git_indexer *idx, git_transfer_progress *stats)
+{
+ if (idx->progress_cb)
+ return giterr_set_after_callback_function(
+ idx->progress_cb(stats, idx->progress_payload),
+ "indexer progress");
+ return 0;
+}
+
+/* Hash everything but the last 20B of input */
+static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size)
+{
+ size_t to_expell, to_keep;
+
+ if (size == 0)
+ return;
+
+ /* Easy case, dump the buffer and the data minus the last 20 bytes */
+ if (size >= GIT_OID_RAWSZ) {
+ git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len);
+ git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ);
+
+ data += size - GIT_OID_RAWSZ;
+ memcpy(idx->inbuf, data, GIT_OID_RAWSZ);
+ idx->inbuf_len = GIT_OID_RAWSZ;
+ return;
+ }
+
+ /* We can just append */
+ if (idx->inbuf_len + size <= GIT_OID_RAWSZ) {
+ memcpy(idx->inbuf + idx->inbuf_len, data, size);
+ idx->inbuf_len += size;
+ return;
+ }
+
+ /* We need to partially drain the buffer and then append */
+ to_keep = GIT_OID_RAWSZ - size;
+ to_expell = idx->inbuf_len - to_keep;
+
+ git_hash_update(&idx->trailer, idx->inbuf, to_expell);
+
+ memmove(idx->inbuf, idx->inbuf + to_expell, to_keep);
+ memcpy(idx->inbuf + to_keep, data, size);
+ idx->inbuf_len += size - to_expell;
+}
+
+static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size)
+{
+ git_file fd = idx->pack->mwf.fd;
+ size_t mmap_alignment;
+ size_t page_offset;
+ git_off_t page_start;
+ unsigned char *map_data;
+ git_map map;
+ int error;
+
+ assert(data && size);
+
+ if ((error = git__mmap_alignment(&mmap_alignment)) < 0)
+ return error;
+
+ /* the offset needs to be at the mmap boundary for the platform */
+ page_offset = offset % mmap_alignment;
+ page_start = offset - page_offset;
+
+ if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0)
+ return error;
+
+ map_data = (unsigned char *)map.data;
+ memcpy(map_data + page_offset, data, size);
+ p_munmap(&map);
+
+ return 0;
+}
+
+static int append_to_pack(git_indexer *idx, const void *data, size_t size)
+{
+ git_off_t current_size = idx->pack->mwf.size;
+ int fd = idx->pack->mwf.fd;
+
+ if (!size)
+ return 0;
+
+ if (p_lseek(fd, current_size + size - 1, SEEK_SET) < 0 ||
+ p_write(idx->pack->mwf.fd, data, 1) < 0) {
+ giterr_set(GITERR_OS, "cannot extend packfile '%s'", idx->pack->pack_name);
+ return -1;
+ }
+
+ return write_at(idx, data, idx->pack->mwf.size, size);
+}
+
+int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats)
+{
+ int error = -1;
+ size_t processed;
+ struct git_pack_header *hdr = &idx->hdr;
+ git_mwindow_file *mwf = &idx->pack->mwf;
+
+ assert(idx && data && stats);
+
+ processed = stats->indexed_objects;
+
+ if ((error = append_to_pack(idx, data, size)) < 0)
+ return error;
+
+ hash_partially(idx, data, (int)size);
+
+ /* Make sure we set the new size of the pack */
+ idx->pack->mwf.size += size;
+
+ if (!idx->parsed_header) {
+ unsigned int total_objects;
+
+ if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header))
+ return 0;
+
+ if ((error = parse_header(&idx->hdr, idx->pack)) < 0)
+ return error;
+
+ idx->parsed_header = 1;
+ idx->nr_objects = ntohl(hdr->hdr_entries);
+ idx->off = sizeof(struct git_pack_header);
+
+ /* for now, limit to 2^32 objects */
+ assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));
+ if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects))
+ total_objects = (unsigned int)idx->nr_objects;
+ else
+ total_objects = UINT_MAX;
+
+ idx->pack->idx_cache = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(idx->pack->idx_cache);
+
+ idx->pack->has_cache = 1;
+ if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0)
+ return -1;
+
+ if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0)
+ return -1;
+
+ stats->received_objects = 0;
+ stats->local_objects = 0;
+ stats->total_deltas = 0;
+ stats->indexed_deltas = 0;
+ processed = stats->indexed_objects = 0;
+ stats->total_objects = total_objects;
+
+ if ((error = do_progress_callback(idx, stats)) != 0)
+ return error;
+ }
+
+ /* Now that we have data in the pack, let's try to parse it */
+
+ /* As the file grows any windows we try to use will be out of date */
+ git_mwindow_free_all(mwf);
+
+ while (processed < idx->nr_objects) {
+ git_packfile_stream *stream = &idx->stream;
+ git_off_t entry_start = idx->off;
+ size_t entry_size;
+ git_otype type;
+ git_mwindow *w = NULL;
+
+ if (idx->pack->mwf.size <= idx->off + 20)
+ return 0;
+
+ if (!idx->have_stream) {
+ error = git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
+ return 0;
+ }
+ if (error < 0)
+ goto on_error;
+
+ git_mwindow_close(&w);
+ idx->entry_start = entry_start;
+ git_hash_init(&idx->hash_ctx);
+
+ if (type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA) {
+ error = advance_delta_offset(idx, type);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
+ return 0;
+ }
+ if (error < 0)
+ goto on_error;
+
+ idx->have_delta = 1;
+ } else {
+ idx->have_delta = 0;
+ hash_header(&idx->hash_ctx, entry_size, type);
+ }
+
+ idx->have_stream = 1;
+
+ error = git_packfile_stream_open(stream, idx->pack, idx->off);
+ if (error < 0)
+ goto on_error;
+ }
+
+ if (idx->have_delta) {
+ error = read_object_stream(idx, stream);
+ } else {
+ error = hash_object_stream(idx, stream);
+ }
+
+ idx->off = stream->curpos;
+ if (error == GIT_EBUFS)
+ return 0;
+
+ /* We want to free the stream reasorces no matter what here */
+ idx->have_stream = 0;
+ git_packfile_stream_free(stream);
+
+ if (error < 0)
+ goto on_error;
+
+ if (idx->have_delta) {
+ error = store_delta(idx);
+ } else {
+ error = store_object(idx);
+ }
+
+ if (error < 0)
+ goto on_error;
+
+ if (!idx->have_delta) {
+ stats->indexed_objects = (unsigned int)++processed;
+ }
+ stats->received_objects++;
+
+ if ((error = do_progress_callback(idx, stats)) != 0)
+ goto on_error;
+ }
+
+ return 0;
+
+on_error:
+ git_mwindow_free_all(mwf);
+ return error;
+}
+
+static int index_path(git_buf *path, git_indexer *idx, const char *suffix)
+{
+ const char prefix[] = "pack-";
+ size_t slash = (size_t)path->size;
+
+ /* search backwards for '/' */
+ while (slash > 0 && path->ptr[slash - 1] != '/')
+ slash--;
+
+ if (git_buf_grow(path, slash + 1 + strlen(prefix) +
+ GIT_OID_HEXSZ + strlen(suffix) + 1) < 0)
+ return -1;
+
+ git_buf_truncate(path, slash);
+ git_buf_puts(path, prefix);
+ git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash);
+ path->size += GIT_OID_HEXSZ;
+ git_buf_puts(path, suffix);
+
+ return git_buf_oom(path) ? -1 : 0;
+}
+
+/**
+ * Rewind the packfile by the trailer, as we might need to fix the
+ * packfile by injecting objects at the tail and must overwrite it.
+ */
+static void seek_back_trailer(git_indexer *idx)
+{
+ idx->pack->mwf.size -= GIT_OID_RAWSZ;
+ git_mwindow_free_all(&idx->pack->mwf);
+}
+
+static int inject_object(git_indexer *idx, git_oid *id)
+{
+ git_odb_object *obj;
+ struct entry *entry;
+ struct git_pack_entry *pentry = NULL;
+ git_oid foo = {{0}};
+ unsigned char hdr[64];
+ git_buf buf = GIT_BUF_INIT;
+ git_off_t entry_start;
+ const void *data;
+ size_t len, hdr_len;
+ int error;
+
+ seek_back_trailer(idx);
+ entry_start = idx->pack->mwf.size;
+
+ if (git_odb_read(&obj, idx->odb, id) < 0) {
+ giterr_set(GITERR_INDEXER, "missing delta bases");
+ return -1;
+ }
+
+ data = git_odb_object_data(obj);
+ len = git_odb_object_size(obj);
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->crc = crc32(0L, Z_NULL, 0);
+
+ /* Write out the object header */
+ hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj));
+ if ((error = append_to_pack(idx, hdr, hdr_len)) < 0)
+ goto cleanup;
+
+ idx->pack->mwf.size += hdr_len;
+ entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len);
+
+ if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0)
+ goto cleanup;
+
+ /* And then the compressed object */
+ if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0)
+ goto cleanup;
+
+ idx->pack->mwf.size += buf.size;
+ entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size));
+ git_buf_free(&buf);
+
+ /* Write a fake trailer so the pack functions play ball */
+
+ if ((error = append_to_pack(idx, &foo, GIT_OID_RAWSZ)) < 0)
+ goto cleanup;
+
+ idx->pack->mwf.size += GIT_OID_RAWSZ;
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_oid_cpy(&pentry->sha1, id);
+ git_oid_cpy(&entry->oid, id);
+ idx->off = entry_start + hdr_len + len;
+
+ error = save_entry(idx, entry, pentry, entry_start);
+
+cleanup:
+ if (error) {
+ git__free(entry);
+ git__free(pentry);
+ }
+
+ git_odb_object_free(obj);
+ return error;
+}
+
+static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats)
+{
+ int error, found_ref_delta = 0;
+ unsigned int i;
+ struct delta_info *delta;
+ size_t size;
+ git_otype type;
+ git_mwindow *w = NULL;
+ git_off_t curpos = 0;
+ unsigned char *base_info;
+ unsigned int left = 0;
+ git_oid base;
+
+ assert(git_vector_length(&idx->deltas) > 0);
+
+ if (idx->odb == NULL) {
+ giterr_set(GITERR_INDEXER, "cannot fix a thin pack without an ODB");
+ return -1;
+ }
+
+ /* Loop until we find the first REF delta */
+ git_vector_foreach(&idx->deltas, i, delta) {
+ if (!delta)
+ continue;
+
+ curpos = delta->delta_off;
+ error = git_packfile_unpack_header(&size, &type, &idx->pack->mwf, &w, &curpos);
+ if (error < 0)
+ return error;
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ found_ref_delta = 1;
+ break;
+ }
+ }
+
+ if (!found_ref_delta) {
+ giterr_set(GITERR_INDEXER, "no REF_DELTA found, cannot inject object");
+ return -1;
+ }
+
+ /* curpos now points to the base information, which is an OID */
+ base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left);
+ if (base_info == NULL) {
+ giterr_set(GITERR_INDEXER, "failed to map delta information");
+ return -1;
+ }
+
+ git_oid_fromraw(&base, base_info);
+ git_mwindow_close(&w);
+
+ if (has_entry(idx, &base))
+ return 0;
+
+ if (inject_object(idx, &base) < 0)
+ return -1;
+
+ stats->local_objects++;
+
+ return 0;
+}
+
+static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
+{
+ unsigned int i;
+ struct delta_info *delta;
+ int progressed = 0, non_null = 0, progress_cb_result;
+
+ while (idx->deltas.length > 0) {
+ progressed = 0;
+ non_null = 0;
+ git_vector_foreach(&idx->deltas, i, delta) {
+ git_rawobj obj = {NULL};
+
+ if (!delta)
+ continue;
+
+ non_null = 1;
+ idx->off = delta->delta_off;
+ if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0)
+ continue;
+
+ if (hash_and_save(idx, &obj, delta->delta_off) < 0)
+ continue;
+
+ git__free(obj.data);
+ stats->indexed_objects++;
+ stats->indexed_deltas++;
+ progressed = 1;
+ if ((progress_cb_result = do_progress_callback(idx, stats)) < 0)
+ return progress_cb_result;
+
+ /* remove from the list */
+ git_vector_set(NULL, &idx->deltas, i, NULL);
+ git__free(delta);
+ }
+
+ /* if none were actually set, we're done */
+ if (!non_null)
+ break;
+
+ if (!progressed && (fix_thin_pack(idx, stats) < 0)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *stats)
+{
+ void *ptr;
+ size_t chunk = 1024*1024;
+ git_off_t hashed = 0;
+ git_mwindow *w = NULL;
+ git_mwindow_file *mwf;
+ unsigned int left;
+
+ mwf = &idx->pack->mwf;
+
+ git_hash_init(&idx->trailer);
+
+
+ /* Update the header to include the numer of local objects we injected */
+ idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects);
+ if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0)
+ return -1;
+
+ /*
+ * We now use the same technique as before to determine the
+ * hash. We keep reading up to the end and let
+ * hash_partially() keep the existing trailer out of the
+ * calculation.
+ */
+ git_mwindow_free_all(mwf);
+ idx->inbuf_len = 0;
+ while (hashed < mwf->size) {
+ ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left);
+ if (ptr == NULL)
+ return -1;
+
+ hash_partially(idx, ptr, left);
+ hashed += left;
+
+ git_mwindow_close(&w);
+ }
+
+ return 0;
+}
+
+int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats)
+{
+ git_mwindow *w = NULL;
+ unsigned int i, long_offsets = 0, left;
+ int error;
+ struct git_pack_idx_header hdr;
+ git_buf filename = GIT_BUF_INIT;
+ struct entry *entry;
+ git_oid trailer_hash, file_hash;
+ git_hash_ctx ctx;
+ git_filebuf index_file = {0};
+ void *packfile_trailer;
+
+ if (!idx->parsed_header) {
+ giterr_set(GITERR_INDEXER, "incomplete pack header");
+ return -1;
+ }
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
+
+ /* Test for this before resolve_deltas(), as it plays with idx->off */
+ if (idx->off + 20 < idx->pack->mwf.size) {
+ giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack");
+ return -1;
+ }
+
+ packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
+ if (packfile_trailer == NULL) {
+ git_mwindow_close(&w);
+ goto on_error;
+ }
+
+ /* Compare the packfile trailer as it was sent to us and what we calculated */
+ git_oid_fromraw(&file_hash, packfile_trailer);
+ git_mwindow_close(&w);
+
+ git_hash_final(&trailer_hash, &idx->trailer);
+ if (git_oid_cmp(&file_hash, &trailer_hash)) {
+ giterr_set(GITERR_INDEXER, "packfile trailer mismatch");
+ return -1;
+ }
+
+ /* Freeze the number of deltas */
+ stats->total_deltas = stats->total_objects - stats->indexed_objects;
+
+ if ((error = resolve_deltas(idx, stats)) < 0)
+ return error;
+
+ if (stats->indexed_objects != stats->total_objects) {
+ giterr_set(GITERR_INDEXER, "early EOF");
+ return -1;
+ }
+
+ if (stats->local_objects > 0) {
+ if (update_header_and_rehash(idx, stats) < 0)
+ return -1;
+
+ git_hash_final(&trailer_hash, &idx->trailer);
+ write_at(idx, &trailer_hash, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ);
+ }
+
+ git_vector_sort(&idx->objects);
+
+ git_buf_sets(&filename, idx->pack->pack_name);
+ git_buf_shorten(&filename, strlen("pack"));
+ git_buf_puts(&filename, "idx");
+ if (git_buf_oom(&filename))
+ return -1;
+
+ if (git_filebuf_open(&index_file, filename.ptr,
+ GIT_FILEBUF_HASH_CONTENTS, idx->mode) < 0)
+ goto on_error;
+
+ /* Write out the header */
+ hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+ hdr.idx_version = htonl(2);
+ git_filebuf_write(&index_file, &hdr, sizeof(hdr));
+
+ /* Write out the fanout table */
+ for (i = 0; i < 256; ++i) {
+ uint32_t n = htonl(idx->fanout[i]);
+ git_filebuf_write(&index_file, &n, sizeof(n));
+ }
+
+ /* Write out the object names (SHA-1 hashes) */
+ git_vector_foreach(&idx->objects, i, entry) {
+ git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid));
+ git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ);
+ }
+ git_hash_final(&idx->hash, &ctx);
+
+ /* Write out the CRC32 values */
+ git_vector_foreach(&idx->objects, i, entry) {
+ git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t));
+ }
+
+ /* Write out the offsets */
+ git_vector_foreach(&idx->objects, i, entry) {
+ uint32_t n;
+
+ if (entry->offset == UINT32_MAX)
+ n = htonl(0x80000000 | long_offsets++);
+ else
+ n = htonl(entry->offset);
+
+ git_filebuf_write(&index_file, &n, sizeof(uint32_t));
+ }
+
+ /* Write out the long offsets */
+ git_vector_foreach(&idx->objects, i, entry) {
+ uint32_t split[2];
+
+ if (entry->offset != UINT32_MAX)
+ continue;
+
+ split[0] = htonl(entry->offset_long >> 32);
+ split[1] = htonl(entry->offset_long & 0xffffffff);
+
+ git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2);
+ }
+
+ /* Write out the packfile trailer to the index */
+ if (git_filebuf_write(&index_file, &trailer_hash, GIT_OID_RAWSZ) < 0)
+ goto on_error;
+
+ /* Write out the hash of the idx */
+ if (git_filebuf_hash(&trailer_hash, &index_file) < 0)
+ goto on_error;
+
+ git_filebuf_write(&index_file, &trailer_hash, sizeof(git_oid));
+
+ /* Figure out what the final name should be */
+ if (index_path(&filename, idx, ".idx") < 0)
+ goto on_error;
+
+ /* Commit file */
+ if (git_filebuf_commit_at(&index_file, filename.ptr) < 0)
+ goto on_error;
+
+ git_mwindow_free_all(&idx->pack->mwf);
+ /* We need to close the descriptor here so Windows doesn't choke on commit_at */
+ if (p_close(idx->pack->mwf.fd) < 0) {
+ giterr_set(GITERR_OS, "failed to close packfile");
+ goto on_error;
+ }
+
+ idx->pack->mwf.fd = -1;
+
+ if (index_path(&filename, idx, ".pack") < 0)
+ goto on_error;
+
+ /* And don't forget to rename the packfile to its new place. */
+ p_rename(idx->pack->pack_name, git_buf_cstr(&filename));
+
+ git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
+ return 0;
+
+on_error:
+ git_mwindow_free_all(&idx->pack->mwf);
+ git_filebuf_cleanup(&index_file);
+ git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
+ return -1;
+}
+
+void git_indexer_free(git_indexer *idx)
+{
+ if (idx == NULL)
+ return;
+
+ git_vector_free_deep(&idx->objects);
+
+ if (idx->pack && idx->pack->idx_cache) {
+ struct git_pack_entry *pentry;
+ kh_foreach_value(
+ idx->pack->idx_cache, pentry, { git__free(pentry); });
+
+ git_oidmap_free(idx->pack->idx_cache);
+ }
+
+ git_vector_free_deep(&idx->deltas);
+
+ if (!git_mutex_lock(&git__mwindow_mutex)) {
+ git_packfile_free(idx->pack);
+ git_mutex_unlock(&git__mwindow_mutex);
+ }
+
+ git_hash_ctx_cleanup(&idx->trailer);
+ git_hash_ctx_cleanup(&idx->hash_ctx);
+ git__free(idx);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_integer_h__
+#define INCLUDE_integer_h__
+
+/** @return true if p fits into the range of a size_t */
+GIT_INLINE(int) git__is_sizet(git_off_t p)
+{
+ size_t r = (size_t)p;
+ return p == (git_off_t)r;
+}
+
+/** @return true if p fits into the range of an ssize_t */
+GIT_INLINE(int) git__is_ssizet(size_t p)
+{
+ ssize_t r = (ssize_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of a uint32_t */
+GIT_INLINE(int) git__is_uint32(size_t p)
+{
+ uint32_t r = (uint32_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of an unsigned long */
+GIT_INLINE(int) git__is_ulong(git_off_t p)
+{
+ unsigned long r = (unsigned long)p;
+ return p == (git_off_t)r;
+}
+
+/** @return true if p fits into the range of an int */
+GIT_INLINE(int) git__is_int(long long p)
+{
+ int r = (int)p;
+ return p == (long long)r;
+}
+
+/**
+ * Sets `one + two` into `out`, unless the arithmetic would overflow.
+ * @return true if the result fits in a `uint64_t`, false on overflow.
+ */
+GIT_INLINE(bool) git__add_uint64_overflow(uint64_t *out, uint64_t one, uint64_t two)
+{
+ if (UINT64_MAX - one < two)
+ return true;
+ *out = one + two;
+ return false;
+}
+
+/* Use clang/gcc compiler intrinsics whenever possible */
+#if (SIZE_MAX == UINT_MAX) && __has_builtin(__builtin_uadd_overflow)
+# define git__add_sizet_overflow(out, one, two) \
+ __builtin_uadd_overflow(one, two, out)
+# define git__multiply_sizet_overflow(out, one, two) \
+ __builtin_umul_overflow(one, two, out)
+#elif (SIZE_MAX == ULONG_MAX) && __has_builtin(__builtin_uaddl_overflow)
+# define git__add_sizet_overflow(out, one, two) \
+ __builtin_uaddl_overflow(one, two, out)
+# define git__multiply_sizet_overflow(out, one, two) \
+ __builtin_umull_overflow(one, two, out)
+#else
+
+/**
+ * Sets `one + two` into `out`, unless the arithmetic would overflow.
+ * @return true if the result fits in a `size_t`, false on overflow.
+ */
+GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two)
+{
+ if (SIZE_MAX - one < two)
+ return true;
+ *out = one + two;
+ return false;
+}
+
+/**
+ * Sets `one * two` into `out`, unless the arithmetic would overflow.
+ * @return true if the result fits in a `size_t`, false on overflow.
+ */
+GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two)
+{
+ if (one && SIZE_MAX / one < two)
+ return true;
+ *out = one * two;
+ return false;
+}
+
+#endif
+
+#endif /* INCLUDE_integer_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "iterator.h"
+#include "tree.h"
+#include "index.h"
+
+#define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
+#define GIT_ITERATOR_HONOR_IGNORES (1 << 16)
+#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17)
+
+#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS)
+#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS)
+#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES)
+#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT)
+
+
+static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
+{
+ if (ignore_case)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
+
+ iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp;
+ iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp;
+ iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp;
+ iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch;
+
+ git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
+}
+
+static int iterator_range_init(
+ git_iterator *iter, const char *start, const char *end)
+{
+ if (start && *start) {
+ iter->start = git__strdup(start);
+ GITERR_CHECK_ALLOC(iter->start);
+
+ iter->start_len = strlen(iter->start);
+ }
+
+ if (end && *end) {
+ iter->end = git__strdup(end);
+ GITERR_CHECK_ALLOC(iter->end);
+
+ iter->end_len = strlen(iter->end);
+ }
+
+ iter->started = (iter->start == NULL);
+ iter->ended = false;
+
+ return 0;
+}
+
+static void iterator_range_free(git_iterator *iter)
+{
+ if (iter->start) {
+ git__free(iter->start);
+ iter->start = NULL;
+ iter->start_len = 0;
+ }
+
+ if (iter->end) {
+ git__free(iter->end);
+ iter->end = NULL;
+ iter->end_len = 0;
+ }
+}
+
+static int iterator_reset_range(
+ git_iterator *iter, const char *start, const char *end)
+{
+ iterator_range_free(iter);
+ return iterator_range_init(iter, start, end);
+}
+
+static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist)
+{
+ size_t i;
+
+ if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < pathlist->count; i++) {
+ if (!pathlist->strings[i])
+ continue;
+
+ if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int iterator_init_common(
+ git_iterator *iter,
+ git_repository *repo,
+ git_index *index,
+ git_iterator_options *given_opts)
+{
+ static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_iterator_options *options = given_opts ? given_opts : &default_opts;
+ bool ignore_case;
+ int precompose;
+ int error;
+
+ iter->repo = repo;
+ iter->index = index;
+ iter->flags = options->flags;
+
+ if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) {
+ ignore_case = true;
+ } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) {
+ ignore_case = false;
+ } else if (repo) {
+ git_index *index;
+
+ if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
+ return error;
+
+ ignore_case = !!index->ignore_case;
+
+ if (ignore_case == 1)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE;
+ } else {
+ ignore_case = false;
+ }
+
+ /* try to look up precompose and set flag if appropriate */
+ if (repo &&
+ (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 &&
+ (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) {
+
+ if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
+ giterr_clear();
+ else if (precompose)
+ iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+ }
+
+ if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND))
+ iter->flags |= GIT_ITERATOR_INCLUDE_TREES;
+
+ if ((error = iterator_range_init(iter, options->start, options->end)) < 0 ||
+ (error = iterator_pathlist_init(iter, &options->pathlist)) < 0)
+ return error;
+
+ iterator_set_ignore_case(iter, ignore_case);
+ return 0;
+}
+
+static void iterator_clear(git_iterator *iter)
+{
+ iter->started = false;
+ iter->ended = false;
+ iter->stat_calls = 0;
+ iter->pathlist_walk_idx = 0;
+ iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+}
+
+GIT_INLINE(bool) iterator_has_started(
+ git_iterator *iter, const char *path, bool is_submodule)
+{
+ size_t path_len;
+
+ if (iter->start == NULL || iter->started == true)
+ return true;
+
+ /* the starting path is generally a prefix - we have started once we
+ * are prefixed by this path
+ */
+ iter->started = (iter->prefixcomp(path, iter->start) >= 0);
+
+ if (iter->started)
+ return true;
+
+ path_len = strlen(path);
+
+ /* if, however, we are a submodule, then we support `start` being
+ * suffixed with a `/` for crazy legacy reasons. match `submod`
+ * with a start path of `submod/`.
+ */
+ if (is_submodule && iter->start_len && path_len == iter->start_len - 1 &&
+ iter->start[iter->start_len-1] == '/')
+ return true;
+
+ /* if, however, our current path is a directory, and our starting path
+ * is _beneath_ that directory, then recurse into the directory (even
+ * though we have not yet "started")
+ */
+ if (path_len > 0 && path[path_len-1] == '/' &&
+ iter->strncomp(path, iter->start, path_len) == 0)
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path)
+{
+ if (iter->end == NULL)
+ return false;
+ else if (iter->ended)
+ return true;
+
+ iter->ended = (iter->prefixcomp(path, iter->end) > 0);
+ return iter->ended;
+}
+
+/* walker for the index and tree iterator that allows it to walk the sorted
+ * pathlist entries alongside sorted iterator entries.
+ */
+static bool iterator_pathlist_next_is(git_iterator *iter, const char *path)
+{
+ char *p;
+ size_t path_len, p_len, cmp_len, i;
+ int cmp;
+
+ if (iter->pathlist.length == 0)
+ return true;
+
+ git_vector_sort(&iter->pathlist);
+
+ path_len = strlen(path);
+
+ /* for comparison, drop the trailing slash on the current '/' */
+ if (path_len && path[path_len-1] == '/')
+ path_len--;
+
+ for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) {
+ p = iter->pathlist.contents[i];
+ p_len = strlen(p);
+
+ if (p_len && p[p_len-1] == '/')
+ p_len--;
+
+ cmp_len = min(path_len, p_len);
+
+ /* see if the pathlist entry is a prefix of this path */
+ cmp = iter->strncomp(p, path, cmp_len);
+
+ /* prefix match - see if there's an exact match, or if we were
+ * given a path that matches the directory
+ */
+ if (cmp == 0) {
+ /* if this pathlist entry is not suffixed with a '/' then
+ * it matches a path that is a file or a directory.
+ * (eg, pathlist = "foo" and path is "foo" or "foo/" or
+ * "foo/something")
+ */
+ if (p[cmp_len] == '\0' &&
+ (path[cmp_len] == '\0' || path[cmp_len] == '/'))
+ return true;
+
+ /* if this pathlist entry _is_ suffixed with a '/' then
+ * it matches only paths that are directories.
+ * (eg, pathlist = "foo/" and path is "foo/" or "foo/something")
+ */
+ if (p[cmp_len] == '/' && path[cmp_len] == '/')
+ return true;
+ }
+
+ /* this pathlist entry sorts before the given path, try the next */
+ else if (cmp < 0) {
+ iter->pathlist_walk_idx++;
+ continue;
+ }
+
+ /* this pathlist sorts after the given path, no match. */
+ else if (cmp > 0) {
+ break;
+ }
+ }
+
+ return false;
+}
+
+typedef enum {
+ ITERATOR_PATHLIST_NONE = 0,
+ ITERATOR_PATHLIST_IS_FILE = 1,
+ ITERATOR_PATHLIST_IS_DIR = 2,
+ ITERATOR_PATHLIST_IS_PARENT = 3,
+ ITERATOR_PATHLIST_FULL = 4,
+} iterator_pathlist_search_t;
+
+static iterator_pathlist_search_t iterator_pathlist_search(
+ git_iterator *iter, const char *path, size_t path_len)
+{
+ const char *p;
+ size_t idx;
+ int error;
+
+ if (iter->pathlist.length == 0)
+ return ITERATOR_PATHLIST_FULL;
+
+ git_vector_sort(&iter->pathlist);
+
+ error = git_vector_bsearch2(&idx, &iter->pathlist,
+ (git_vector_cmp)iter->strcomp, path);
+
+ /* the given path was found in the pathlist. since the pathlist only
+ * matches directories when they're suffixed with a '/', analyze the
+ * path string to determine whether it's a directory or not.
+ */
+ if (error == 0) {
+ if (path_len && path[path_len-1] == '/')
+ return ITERATOR_PATHLIST_IS_DIR;
+
+ return ITERATOR_PATHLIST_IS_FILE;
+ }
+
+ /* at this point, the path we're examining may be a directory (though we
+ * don't know that yet, since we're avoiding a stat unless it's necessary)
+ * so walk the pathlist looking for the given path with a '/' after it,
+ */
+ while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
+ if (iter->prefixcomp(p, path) != 0)
+ break;
+
+ /* an exact match would have been matched by the bsearch above */
+ assert(p[path_len]);
+
+ /* is this a literal directory entry (eg `foo/`) or a file beneath */
+ if (p[path_len] == '/') {
+ return (p[path_len+1] == '\0') ?
+ ITERATOR_PATHLIST_IS_DIR :
+ ITERATOR_PATHLIST_IS_PARENT;
+ }
+
+ if (p[path_len] > '/')
+ break;
+
+ idx++;
+ }
+
+ return ITERATOR_PATHLIST_NONE;
+}
+
+/* Empty iterator */
+
+static int empty_iterator_noop(const git_index_entry **e, git_iterator *i)
+{
+ GIT_UNUSED(i);
+
+ if (e)
+ *e = NULL;
+
+ return GIT_ITEROVER;
+}
+
+static int empty_iterator_advance_over(
+ const git_index_entry **e,
+ git_iterator_status_t *s,
+ git_iterator *i)
+{
+ *s = GIT_ITERATOR_STATUS_EMPTY;
+ return empty_iterator_noop(e, i);
+}
+
+static int empty_iterator_reset(git_iterator *i)
+{
+ GIT_UNUSED(i);
+ return 0;
+}
+
+static void empty_iterator_free(git_iterator *i)
+{
+ GIT_UNUSED(i);
+}
+
+typedef struct {
+ git_iterator base;
+ git_iterator_callbacks cb;
+} empty_iterator;
+
+int git_iterator_for_nothing(
+ git_iterator **out,
+ git_iterator_options *options)
+{
+ empty_iterator *iter;
+
+ static git_iterator_callbacks callbacks = {
+ empty_iterator_noop,
+ empty_iterator_noop,
+ empty_iterator_noop,
+ empty_iterator_advance_over,
+ empty_iterator_reset,
+ empty_iterator_free
+ };
+
+ *out = NULL;
+
+ iter = git__calloc(1, sizeof(empty_iterator));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->base.type = GIT_ITERATOR_TYPE_EMPTY;
+ iter->base.cb = &callbacks;
+ iter->base.flags = options->flags;
+
+ *out = &iter->base;
+ return 0;
+}
+
+/* Tree iterator */
+
+typedef struct {
+ git_tree_entry *tree_entry;
+ const char *parent_path;
+} tree_iterator_entry;
+
+typedef struct {
+ git_tree *tree;
+
+ /* path to this particular frame (folder) */
+ git_buf path;
+
+ /* a sorted list of the entries for this frame (folder), these are
+ * actually pointers to the iterator's entry pool.
+ */
+ git_vector entries;
+ tree_iterator_entry *current;
+
+ size_t next_idx;
+
+ /* on case insensitive iterations, we also have an array of other
+ * paths that were case insensitively equal to this one, and their
+ * tree objects. we have coalesced the tree entries into this frame.
+ * a child `tree_iterator_entry` will contain a pointer to its actual
+ * parent path.
+ */
+ git_vector similar_trees;
+ git_array_t(git_buf) similar_paths;
+} tree_iterator_frame;
+
+typedef struct {
+ git_iterator base;
+ git_tree *root;
+ git_array_t(tree_iterator_frame) frames;
+
+ git_index_entry entry;
+ git_buf entry_path;
+
+ /* a pool of entries to reduce the number of allocations */
+ git_pool entry_pool;
+} tree_iterator;
+
+GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame(
+ tree_iterator *iter)
+{
+ return iter->frames.size > 1 ?
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
+}
+
+GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame(
+ tree_iterator *iter)
+{
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
+}
+
+GIT_INLINE(int) tree_entry_cmp(
+ const git_tree_entry *a, const git_tree_entry *b, bool icase)
+{
+ return git_path_cmp(
+ a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
+ b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
+ icase ? git__strncasecmp : git__strncmp);
+}
+
+GIT_INLINE(int) tree_iterator_entry_cmp(const void *ptr_a, const void *ptr_b)
+{
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
+
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, false);
+}
+
+GIT_INLINE(int) tree_iterator_entry_cmp_icase(
+ const void *ptr_a, const void *ptr_b)
+{
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
+
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, true);
+}
+
+static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b)
+{
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
+
+ int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true);
+
+ /* stabilize the sort order for filenames that are (case insensitively)
+ * the same by examining the parent path (case sensitively) before
+ * falling back to a case sensitive sort of the filename.
+ */
+ if (!c && a->parent_path != b->parent_path)
+ c = git__strcmp(a->parent_path, b->parent_path);
+
+ if (!c)
+ c = tree_entry_cmp(a->tree_entry, b->tree_entry, false);
+
+ return c;
+}
+
+static int tree_iterator_compute_path(
+ git_buf *out,
+ tree_iterator_entry *entry)
+{
+ git_buf_clear(out);
+
+ if (entry->parent_path)
+ git_buf_joinpath(out, entry->parent_path, entry->tree_entry->filename);
+ else
+ git_buf_puts(out, entry->tree_entry->filename);
+
+ if (git_tree_entry__is_tree(entry->tree_entry))
+ git_buf_putc(out, '/');
+
+ if (git_buf_oom(out))
+ return -1;
+
+ return 0;
+}
+
+static int tree_iterator_frame_init(
+ tree_iterator *iter,
+ git_tree *tree,
+ tree_iterator_entry *frame_entry)
+{
+ tree_iterator_frame *new_frame = NULL;
+ tree_iterator_entry *new_entry;
+ git_tree *dup = NULL;
+ git_tree_entry *tree_entry;
+ git_vector_cmp cmp;
+ size_t i;
+ int error = 0;
+
+ new_frame = git_array_alloc(iter->frames);
+ GITERR_CHECK_ALLOC(new_frame);
+
+ memset(new_frame, 0, sizeof(tree_iterator_frame));
+
+ if ((error = git_tree_dup(&dup, tree)) < 0)
+ goto done;
+
+ memset(new_frame, 0x0, sizeof(tree_iterator_frame));
+ new_frame->tree = dup;
+
+ if (frame_entry &&
+ (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0)
+ goto done;
+
+ cmp = iterator__ignore_case(&iter->base) ?
+ tree_iterator_entry_sort_icase : NULL;
+
+ if ((error = git_vector_init(
+ &new_frame->entries, dup->entries.size, cmp)) < 0)
+ goto done;
+
+ git_array_foreach(dup->entries, i, tree_entry) {
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
+ GITERR_CHECK_ALLOC(new_entry);
+
+ new_entry->tree_entry = tree_entry;
+ new_entry->parent_path = new_frame->path.ptr;
+
+ if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0)
+ goto done;
+ }
+
+ git_vector_set_sorted(&new_frame->entries,
+ !iterator__ignore_case(&iter->base));
+
+done:
+ if (error < 0) {
+ git_tree_free(dup);
+ git_array_pop(iter->frames);
+ }
+
+ return error;
+}
+
+GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry(
+ tree_iterator_frame *frame)
+{
+ return frame->current;
+}
+
+GIT_INLINE(int) tree_iterator_frame_push_neighbors(
+ tree_iterator *iter,
+ tree_iterator_frame *parent_frame,
+ tree_iterator_frame *frame,
+ const char *filename)
+{
+ tree_iterator_entry *entry, *new_entry;
+ git_tree *tree = NULL;
+ git_tree_entry *tree_entry;
+ git_buf *path;
+ size_t new_size, i;
+ int error = 0;
+
+ while (parent_frame->next_idx < parent_frame->entries.length) {
+ entry = parent_frame->entries.contents[parent_frame->next_idx];
+
+ if (strcasecmp(filename, entry->tree_entry->filename) != 0)
+ break;
+
+ if ((error = git_tree_lookup(&tree,
+ iter->base.repo, entry->tree_entry->oid)) < 0)
+ break;
+
+ if (git_vector_insert(&parent_frame->similar_trees, tree) < 0)
+ break;
+
+ path = git_array_alloc(parent_frame->similar_paths);
+ GITERR_CHECK_ALLOC(path);
+
+ memset(path, 0, sizeof(git_buf));
+
+ if ((error = tree_iterator_compute_path(path, entry)) < 0)
+ break;
+
+ GITERR_CHECK_ALLOC_ADD(&new_size,
+ frame->entries.length, tree->entries.size);
+ git_vector_size_hint(&frame->entries, new_size);
+
+ git_array_foreach(tree->entries, i, tree_entry) {
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
+ GITERR_CHECK_ALLOC(new_entry);
+
+ new_entry->tree_entry = tree_entry;
+ new_entry->parent_path = path->ptr;
+
+ if ((error = git_vector_insert(&frame->entries, new_entry)) < 0)
+ break;
+ }
+
+ if (error)
+ break;
+
+ parent_frame->next_idx++;
+ }
+
+ return error;
+}
+
+GIT_INLINE(int) tree_iterator_frame_push(
+ tree_iterator *iter, tree_iterator_entry *entry)
+{
+ tree_iterator_frame *parent_frame, *frame;
+ git_tree *tree = NULL;
+ int error;
+
+ if ((error = git_tree_lookup(&tree,
+ iter->base.repo, entry->tree_entry->oid)) < 0 ||
+ (error = tree_iterator_frame_init(iter, tree, entry)) < 0)
+ goto done;
+
+ parent_frame = tree_iterator_parent_frame(iter);
+ frame = tree_iterator_current_frame(iter);
+
+ /* if we're case insensitive, then we may have another directory that
+ * is (case insensitively) equal to this one. coalesce those children
+ * into this tree.
+ */
+ if (iterator__ignore_case(&iter->base))
+ error = tree_iterator_frame_push_neighbors(iter,
+ parent_frame, frame, entry->tree_entry->filename);
+
+done:
+ git_tree_free(tree);
+ return error;
+}
+
+static void tree_iterator_frame_pop(tree_iterator *iter)
+{
+ tree_iterator_frame *frame;
+ git_buf *buf = NULL;
+ git_tree *tree;
+ size_t i;
+
+ assert(iter->frames.size);
+
+ frame = git_array_pop(iter->frames);
+
+ git_vector_free(&frame->entries);
+ git_tree_free(frame->tree);
+
+ do {
+ buf = git_array_pop(frame->similar_paths);
+ git_buf_free(buf);
+ } while (buf != NULL);
+
+ git_array_clear(frame->similar_paths);
+
+ git_vector_foreach(&frame->similar_trees, i, tree)
+ git_tree_free(tree);
+
+ git_vector_free(&frame->similar_trees);
+
+ git_buf_free(&frame->path);
+}
+
+static int tree_iterator_current(
+ const git_index_entry **out, git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
+
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
+
+ if (!iter->frames.size) {
+ *out = NULL;
+ return GIT_ITEROVER;
+ }
+
+ *out = &iter->entry;
+ return 0;
+}
+
+static void tree_iterator_set_current(
+ tree_iterator *iter,
+ tree_iterator_frame *frame,
+ tree_iterator_entry *entry)
+{
+ git_tree_entry *tree_entry = entry->tree_entry;
+
+ frame->current = entry;
+
+ memset(&iter->entry, 0x0, sizeof(git_index_entry));
+
+ iter->entry.mode = tree_entry->attr;
+ iter->entry.path = iter->entry_path.ptr;
+ git_oid_cpy(&iter->entry.id, tree_entry->oid);
+}
+
+static int tree_iterator_advance(const git_index_entry **out, git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
+ int error = 0;
+
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ /* examine tree entries until we find the next one to return */
+ while (true) {
+ tree_iterator_entry *prev_entry, *entry;
+ tree_iterator_frame *frame;
+ bool is_tree;
+
+ if ((frame = tree_iterator_current_frame(iter)) == NULL) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* no more entries in this frame. pop the frame out */
+ if (frame->next_idx == frame->entries.length) {
+ tree_iterator_frame_pop(iter);
+ continue;
+ }
+
+ /* we may have coalesced the contents of case-insensitively same-named
+ * directories, so do the sort now.
+ */
+ if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries))
+ git_vector_sort(&frame->entries);
+
+ /* we have more entries in the current frame, that's our next entry */
+ prev_entry = tree_iterator_current_entry(frame);
+ entry = frame->entries.contents[frame->next_idx];
+ frame->next_idx++;
+
+ /* we can have collisions when iterating case insensitively. (eg,
+ * 'A/a' and 'a/A'). squash this one if it's already been seen.
+ */
+ if (iterator__ignore_case(&iter->base) &&
+ prev_entry &&
+ tree_iterator_entry_cmp_icase(prev_entry, entry) == 0)
+ continue;
+
+ if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0)
+ break;
+
+ /* if this path is before our start, advance over this entry */
+ if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false))
+ continue;
+
+ /* if this path is after our end, stop */
+ if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* if we have a list of paths we're interested in, examine it */
+ if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr))
+ continue;
+
+ is_tree = git_tree_entry__is_tree(entry->tree_entry);
+
+ /* if we are *not* including trees then advance over this entry */
+ if (is_tree && !iterator__include_trees(iter)) {
+
+ /* if we've found a tree (and are not returning it to the caller)
+ * and we are autoexpanding, then we want to return the first
+ * child. push the new directory and advance.
+ */
+ if (iterator__do_autoexpand(iter)) {
+ if ((error = tree_iterator_frame_push(iter, entry)) < 0)
+ break;
+ }
+
+ continue;
+ }
+
+ tree_iterator_set_current(iter, frame, entry);
+
+ /* if we are autoexpanding, then push this as a new frame, so that
+ * the next call to `advance` will dive into this directory.
+ */
+ if (is_tree && iterator__do_autoexpand(iter))
+ error = tree_iterator_frame_push(iter, entry);
+
+ break;
+ }
+
+ if (out)
+ *out = (error == 0) ? &iter->entry : NULL;
+
+ return error;
+}
+
+static int tree_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
+ tree_iterator_frame *frame;
+ tree_iterator_entry *prev_entry;
+ int error;
+
+ if (out)
+ *out = NULL;
+
+ if ((frame = tree_iterator_current_frame(iter)) == NULL)
+ return GIT_ITEROVER;
+
+ /* get the last seen entry */
+ prev_entry = tree_iterator_current_entry(frame);
+
+ /* it's legal to call advance_into when auto-expand is on. in this case,
+ * we will have pushed a new (empty) frame on to the stack for this
+ * new directory. since it's empty, its current_entry should be null.
+ */
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
+
+ if (prev_entry) {
+ if (!git_tree_entry__is_tree(prev_entry->tree_entry))
+ return 0;
+
+ if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0)
+ return error;
+ }
+
+ /* we've advanced into the directory in question, let advance
+ * find the first entry
+ */
+ return tree_iterator_advance(out, i);
+}
+
+static int tree_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
+{
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ return git_iterator_advance(out, i);
+}
+
+static void tree_iterator_clear(tree_iterator *iter)
+{
+ while (iter->frames.size)
+ tree_iterator_frame_pop(iter);
+
+ git_array_clear(iter->frames);
+
+ git_pool_clear(&iter->entry_pool);
+ git_buf_clear(&iter->entry_path);
+
+ iterator_clear(&iter->base);
+}
+
+static int tree_iterator_init(tree_iterator *iter)
+{
+ int error;
+
+ git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry));
+
+ if ((error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0)
+ return error;
+
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ return 0;
+}
+
+static int tree_iterator_reset(git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
+
+ tree_iterator_clear(iter);
+ return tree_iterator_init(iter);
+}
+
+static void tree_iterator_free(git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
+
+ tree_iterator_clear(iter);
+
+ git_tree_free(iter->root);
+ git_buf_free(&iter->entry_path);
+}
+
+int git_iterator_for_tree(
+ git_iterator **out,
+ git_tree *tree,
+ git_iterator_options *options)
+{
+ tree_iterator *iter;
+ int error;
+
+ static git_iterator_callbacks callbacks = {
+ tree_iterator_current,
+ tree_iterator_advance,
+ tree_iterator_advance_into,
+ tree_iterator_advance_over,
+ tree_iterator_reset,
+ tree_iterator_free
+ };
+
+ *out = NULL;
+
+ if (tree == NULL)
+ return git_iterator_for_nothing(out, options);
+
+ iter = git__calloc(1, sizeof(tree_iterator));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->base.type = GIT_ITERATOR_TYPE_TREE;
+ iter->base.cb = &callbacks;
+
+ if ((error = iterator_init_common(&iter->base,
+ git_tree_owner(tree), NULL, options)) < 0 ||
+ (error = git_tree_dup(&iter->root, tree)) < 0 ||
+ (error = tree_iterator_init(iter)) < 0)
+ goto on_error;
+
+ *out = &iter->base;
+ return 0;
+
+on_error:
+ git_iterator_free(&iter->base);
+ return error;
+}
+
+int git_iterator_current_tree_entry(
+ const git_tree_entry **tree_entry, git_iterator *i)
+{
+ tree_iterator *iter;
+ tree_iterator_frame *frame;
+ tree_iterator_entry *entry;
+
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
+
+ iter = (tree_iterator *)i;
+
+ frame = tree_iterator_current_frame(iter);
+ entry = tree_iterator_current_entry(frame);
+
+ *tree_entry = entry->tree_entry;
+ return 0;
+}
+
+int git_iterator_current_parent_tree(
+ const git_tree **parent_tree, git_iterator *i, size_t depth)
+{
+ tree_iterator *iter;
+ tree_iterator_frame *frame;
+
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
+
+ iter = (tree_iterator *)i;
+
+ assert(depth < iter->frames.size);
+ frame = &iter->frames.ptr[iter->frames.size-depth-1];
+
+ *parent_tree = frame->tree;
+ return 0;
+}
+
+/* Filesystem iterator */
+
+typedef struct {
+ struct stat st;
+ size_t path_len;
+ iterator_pathlist_search_t match;
+ char path[GIT_FLEX_ARRAY];
+} filesystem_iterator_entry;
+
+typedef struct {
+ git_vector entries;
+ git_pool entry_pool;
+ size_t next_idx;
+
+ size_t path_len;
+ int is_ignored;
+} filesystem_iterator_frame;
+
+typedef struct {
+ git_iterator base;
+ char *root;
+ size_t root_len;
+
+ unsigned int dirload_flags;
+
+ git_tree *tree;
+ git_index *index;
+ git_vector index_snapshot;
+
+ git_array_t(filesystem_iterator_frame) frames;
+ git_ignores ignores;
+
+ /* info about the current entry */
+ git_index_entry entry;
+ git_buf current_path;
+ int current_is_ignored;
+
+ /* temporary buffer for advance_over */
+ git_buf tmp_buf;
+} filesystem_iterator;
+
+
+GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame(
+ filesystem_iterator *iter)
+{
+ return iter->frames.size > 1 ?
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
+}
+
+GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame(
+ filesystem_iterator *iter)
+{
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
+}
+
+GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry(
+ filesystem_iterator_frame *frame)
+{
+ return frame->next_idx == 0 ?
+ NULL : frame->entries.contents[frame->next_idx-1];
+}
+
+static int filesystem_iterator_entry_cmp(const void *_a, const void *_b)
+{
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
+
+ return git__strcmp(a->path, b->path);
+}
+
+static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b)
+{
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
+
+ return git__strcasecmp(a->path, b->path);
+}
+
+#define FILESYSTEM_MAX_DEPTH 100
+
+/**
+ * Figure out if an entry is a submodule.
+ *
+ * We consider it a submodule if the path is listed as a submodule in
+ * either the tree or the index.
+ */
+static int filesystem_iterator_is_submodule(
+ bool *out, filesystem_iterator *iter, const char *path, size_t path_len)
+{
+ bool is_submodule = false;
+ int error;
+
+ *out = false;
+
+ /* first see if this path is a submodule in HEAD */
+ if (iter->tree) {
+ git_tree_entry *entry;
+
+ error = git_tree_entry_bypath(&entry, iter->tree, path);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ if (!error) {
+ is_submodule = (entry->attr == GIT_FILEMODE_COMMIT);
+ git_tree_entry_free(entry);
+ }
+ }
+
+ if (!is_submodule && iter->base.index) {
+ size_t pos;
+
+ error = git_index_snapshot_find(&pos,
+ &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ if (!error) {
+ git_index_entry *e = git_vector_get(&iter->index_snapshot, pos);
+ is_submodule = (e->mode == GIT_FILEMODE_COMMIT);
+ }
+ }
+
+ *out = is_submodule;
+ return 0;
+}
+
+static void filesystem_iterator_frame_push_ignores(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry,
+ filesystem_iterator_frame *new_frame)
+{
+ filesystem_iterator_frame *previous_frame;
+ const char *path = frame_entry ? frame_entry->path : "";
+
+ if (!iterator__honor_ignores(&iter->base))
+ return;
+
+ if (git_ignore__lookup(&new_frame->is_ignored,
+ &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) {
+ giterr_clear();
+ new_frame->is_ignored = GIT_IGNORE_NOTFOUND;
+ }
+
+ /* if this is not the top level directory... */
+ if (frame_entry) {
+ const char *relative_path;
+
+ previous_frame = filesystem_iterator_parent_frame(iter);
+
+ /* push new ignores for files in this directory */
+ relative_path = frame_entry->path + previous_frame->path_len;
+
+ /* inherit ignored from parent if no rule specified */
+ if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND)
+ new_frame->is_ignored = previous_frame->is_ignored;
+
+ git_ignore__push_dir(&iter->ignores, relative_path);
+ }
+}
+
+static void filesystem_iterator_frame_pop_ignores(
+ filesystem_iterator *iter)
+{
+ if (iterator__honor_ignores(&iter->base))
+ git_ignore__pop_dir(&iter->ignores);
+}
+
+GIT_INLINE(bool) filesystem_iterator_examine_path(
+ bool *is_dir_out,
+ iterator_pathlist_search_t *match_out,
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry,
+ const char *path,
+ size_t path_len)
+{
+ bool is_dir = 0;
+ iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL;
+
+ *is_dir_out = false;
+ *match_out = ITERATOR_PATHLIST_NONE;
+
+ if (iter->base.start_len) {
+ int cmp = iter->base.strncomp(path, iter->base.start, path_len);
+
+ /* we haven't stat'ed `path` yet, so we don't yet know if it's a
+ * directory or not. special case if the current path may be a
+ * directory that matches the start prefix.
+ */
+ if (cmp == 0) {
+ if (iter->base.start[path_len] == '/')
+ is_dir = true;
+
+ else if (iter->base.start[path_len] != '\0')
+ cmp = -1;
+ }
+
+ if (cmp < 0)
+ return false;
+ }
+
+ if (iter->base.end_len) {
+ int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len);
+
+ if (cmp > 0)
+ return false;
+ }
+
+ /* if we have a pathlist that we're limiting to, examine this path now
+ * to avoid a `stat` if we're not interested in the path.
+ */
+ if (iter->base.pathlist.length) {
+ /* if our parent was explicitly included, so too are we */
+ if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT)
+ match = ITERATOR_PATHLIST_FULL;
+ else
+ match = iterator_pathlist_search(&iter->base, path, path_len);
+
+ if (match == ITERATOR_PATHLIST_NONE)
+ return false;
+
+ /* Ensure that the pathlist entry lines up with what we expected */
+ if (match == ITERATOR_PATHLIST_IS_DIR ||
+ match == ITERATOR_PATHLIST_IS_PARENT)
+ is_dir = true;
+ }
+
+ *is_dir_out = is_dir;
+ *match_out = match;
+ return true;
+}
+
+GIT_INLINE(bool) filesystem_iterator_is_dot_git(
+ filesystem_iterator *iter, const char *path, size_t path_len)
+{
+ size_t len;
+
+ if (!iterator__ignore_dot_git(&iter->base))
+ return false;
+
+ if ((len = path_len) < 4)
+ return false;
+
+ if (path[len - 1] == '/')
+ len--;
+
+ if (git__tolower(path[len - 1]) != 't' ||
+ git__tolower(path[len - 2]) != 'i' ||
+ git__tolower(path[len - 3]) != 'g' ||
+ git__tolower(path[len - 4]) != '.')
+ return false;
+
+ return (len == 4 || path[len - 5] == '/');
+}
+
+static filesystem_iterator_entry *filesystem_iterator_entry_init(
+ filesystem_iterator_frame *frame,
+ const char *path,
+ size_t path_len,
+ struct stat *statbuf,
+ iterator_pathlist_search_t pathlist_match)
+{
+ filesystem_iterator_entry *entry;
+ size_t entry_size;
+
+ /* Make sure to append two bytes, one for the path's null
+ * termination, one for a possible trailing '/' for folders.
+ */
+ if (GIT_ADD_SIZET_OVERFLOW(&entry_size,
+ sizeof(filesystem_iterator_entry), path_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&entry_size, entry_size, 2) ||
+ (entry = git_pool_malloc(&frame->entry_pool, entry_size)) == NULL)
+ return NULL;
+
+ entry->path_len = path_len;
+ entry->match = pathlist_match;
+ memcpy(entry->path, path, path_len);
+ memcpy(&entry->st, statbuf, sizeof(struct stat));
+
+ /* Suffix directory paths with a '/' */
+ if (S_ISDIR(entry->st.st_mode))
+ entry->path[entry->path_len++] = '/';
+
+ entry->path[entry->path_len] = '\0';
+
+ return entry;
+}
+
+static int filesystem_iterator_frame_push(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry)
+{
+ filesystem_iterator_frame *new_frame = NULL;
+ git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
+ git_buf root = GIT_BUF_INIT;
+ const char *path;
+ filesystem_iterator_entry *entry;
+ struct stat statbuf;
+ size_t path_len;
+ int error;
+
+ if (iter->frames.size == FILESYSTEM_MAX_DEPTH) {
+ giterr_set(GITERR_REPOSITORY,
+ "directory nesting too deep (%"PRIuZ")", iter->frames.size);
+ return -1;
+ }
+
+ new_frame = git_array_alloc(iter->frames);
+ GITERR_CHECK_ALLOC(new_frame);
+
+ memset(new_frame, 0, sizeof(filesystem_iterator_frame));
+
+ if (frame_entry)
+ git_buf_joinpath(&root, iter->root, frame_entry->path);
+ else
+ git_buf_puts(&root, iter->root);
+
+ if (git_buf_oom(&root)) {
+ error = -1;
+ goto done;
+ }
+
+ new_frame->path_len = frame_entry ? frame_entry->path_len : 0;
+
+ /* Any error here is equivalent to the dir not existing, skip over it */
+ if ((error = git_path_diriter_init(
+ &diriter, root.ptr, iter->dirload_flags)) < 0) {
+ error = GIT_ENOTFOUND;
+ goto done;
+ }
+
+ if ((error = git_vector_init(&new_frame->entries, 64,
+ iterator__ignore_case(&iter->base) ?
+ filesystem_iterator_entry_cmp_icase :
+ filesystem_iterator_entry_cmp)) < 0)
+ goto done;
+
+ git_pool_init(&new_frame->entry_pool, 1);
+
+ /* check if this directory is ignored */
+ filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame);
+
+ while ((error = git_path_diriter_next(&diriter)) == 0) {
+ iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL;
+ bool dir_expected = false;
+
+ if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
+ goto done;
+
+ assert(path_len > iter->root_len);
+
+ /* remove the prefix if requested */
+ path += iter->root_len;
+ path_len -= iter->root_len;
+
+ /* examine start / end and the pathlist to see if this path is in it.
+ * note that since we haven't yet stat'ed the path, we cannot know
+ * whether it's a directory yet or not, so this can give us an
+ * expected type (S_IFDIR or S_IFREG) that we should examine)
+ */
+ if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match,
+ iter, frame_entry, path, path_len))
+ continue;
+
+ /* TODO: don't need to stat if assume unchanged for this path and
+ * we have an index, we can just copy the data out of it.
+ */
+
+ if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) {
+ /* file was removed between readdir and lstat */
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ /* treat the file as unreadable */
+ memset(&statbuf, 0, sizeof(statbuf));
+ statbuf.st_mode = GIT_FILEMODE_UNREADABLE;
+
+ error = 0;
+ }
+
+ iter->base.stat_calls++;
+
+ /* Ignore wacky things in the filesystem */
+ if (!S_ISDIR(statbuf.st_mode) &&
+ !S_ISREG(statbuf.st_mode) &&
+ !S_ISLNK(statbuf.st_mode) &&
+ statbuf.st_mode != GIT_FILEMODE_UNREADABLE)
+ continue;
+
+ if (filesystem_iterator_is_dot_git(iter, path, path_len))
+ continue;
+
+ /* convert submodules to GITLINK and remove trailing slashes */
+ if (S_ISDIR(statbuf.st_mode)) {
+ bool submodule = false;
+
+ if ((error = filesystem_iterator_is_submodule(&submodule,
+ iter, path, path_len)) < 0)
+ goto done;
+
+ if (submodule)
+ statbuf.st_mode = GIT_FILEMODE_COMMIT;
+ }
+
+ /* Ensure that the pathlist entry lines up with what we expected */
+ else if (dir_expected)
+ continue;
+
+ entry = filesystem_iterator_entry_init(new_frame,
+ path, path_len, &statbuf, pathlist_match);
+ GITERR_CHECK_ALLOC(entry);
+
+ git_vector_insert(&new_frame->entries, entry);
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ /* sort now that directory suffix is added */
+ git_vector_sort(&new_frame->entries);
+
+done:
+ if (error < 0)
+ git_array_pop(iter->frames);
+
+ git_buf_free(&root);
+ git_path_diriter_free(&diriter);
+ return error;
+}
+
+GIT_INLINE(void) filesystem_iterator_frame_pop(filesystem_iterator *iter)
+{
+ filesystem_iterator_frame *frame;
+
+ assert(iter->frames.size);
+
+ frame = git_array_pop(iter->frames);
+ filesystem_iterator_frame_pop_ignores(iter);
+
+ git_pool_clear(&frame->entry_pool);
+ git_vector_free(&frame->entries);
+}
+
+static void filesystem_iterator_set_current(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *entry)
+{
+ iter->entry.ctime.seconds = entry->st.st_ctime;
+ iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec;
+
+ iter->entry.mtime.seconds = entry->st.st_mtime;
+ iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec;
+
+ iter->entry.dev = entry->st.st_dev;
+ iter->entry.ino = entry->st.st_ino;
+ iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode);
+ iter->entry.uid = entry->st.st_uid;
+ iter->entry.gid = entry->st.st_gid;
+ iter->entry.file_size = entry->st.st_size;
+
+ iter->entry.path = entry->path;
+
+ iter->current_is_ignored = GIT_IGNORE_UNCHECKED;
+}
+
+static int filesystem_iterator_current(
+ const git_index_entry **out, git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
+
+ if (!iter->frames.size) {
+ *out = NULL;
+ return GIT_ITEROVER;
+ }
+
+ *out = &iter->entry;
+ return 0;
+}
+
+static int filesystem_iterator_advance(
+ const git_index_entry **out, git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ int error = 0;
+
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ /* examine filesystem entries until we find the next one to return */
+ while (true) {
+ filesystem_iterator_frame *frame;
+ filesystem_iterator_entry *entry;
+
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* no more entries in this frame. pop the frame out */
+ if (frame->next_idx == frame->entries.length) {
+ filesystem_iterator_frame_pop(iter);
+ continue;
+ }
+
+ /* we have more entries in the current frame, that's our next entry */
+ entry = frame->entries.contents[frame->next_idx];
+ frame->next_idx++;
+
+ if (S_ISDIR(entry->st.st_mode)) {
+ if (iterator__do_autoexpand(iter)) {
+ error = filesystem_iterator_frame_push(iter, entry);
+
+ /* may get GIT_ENOTFOUND due to races or permission problems
+ * that we want to quietly swallow
+ */
+ if (error == GIT_ENOTFOUND)
+ continue;
+ else if (error < 0)
+ break;
+ }
+
+ if (!iterator__include_trees(iter))
+ continue;
+ }
+
+ filesystem_iterator_set_current(iter, entry);
+ break;
+ }
+
+ if (out)
+ *out = (error == 0) ? &iter->entry : NULL;
+
+ return error;
+}
+
+static int filesystem_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *frame;
+ filesystem_iterator_entry *prev_entry;
+ int error;
+
+ if (out)
+ *out = NULL;
+
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL)
+ return GIT_ITEROVER;
+
+ /* get the last seen entry */
+ prev_entry = filesystem_iterator_current_entry(frame);
+
+ /* it's legal to call advance_into when auto-expand is on. in this case,
+ * we will have pushed a new (empty) frame on to the stack for this
+ * new directory. since it's empty, its current_entry should be null.
+ */
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
+
+ if (prev_entry) {
+ if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT &&
+ !S_ISDIR(prev_entry->st.st_mode))
+ return 0;
+
+ if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0)
+ return error;
+ }
+
+ /* we've advanced into the directory in question, let advance
+ * find the first entry
+ */
+ return filesystem_iterator_advance(out, i);
+}
+
+int git_iterator_current_workdir_path(git_buf **out, git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ const git_index_entry *entry;
+
+ if (i->type != GIT_ITERATOR_TYPE_FS &&
+ i->type != GIT_ITERATOR_TYPE_WORKDIR) {
+ *out = NULL;
+ return 0;
+ }
+
+ git_buf_truncate(&iter->current_path, iter->root_len);
+
+ if (git_iterator_current(&entry, i) < 0 ||
+ git_buf_puts(&iter->current_path, entry->path) < 0)
+ return -1;
+
+ *out = &iter->current_path;
+ return 0;
+}
+
+GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry)
+{
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+ return (entry && entry->mode) ?
+ (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) :
+ GIT_DIR_FLAG_UNKNOWN;
+#else
+ GIT_UNUSED(entry);
+ return GIT_DIR_FLAG_UNKNOWN;
+#endif
+}
+
+static void filesystem_iterator_update_ignored(filesystem_iterator *iter)
+{
+ filesystem_iterator_frame *frame;
+ git_dir_flag dir_flag = entry_dir_flag(&iter->entry);
+
+ if (git_ignore__lookup(&iter->current_is_ignored,
+ &iter->ignores, iter->entry.path, dir_flag) < 0) {
+ giterr_clear();
+ iter->current_is_ignored = GIT_IGNORE_NOTFOUND;
+ }
+
+ /* use ignore from containing frame stack */
+ if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) {
+ frame = filesystem_iterator_current_frame(iter);
+ iter->current_is_ignored = frame->is_ignored;
+ }
+}
+
+GIT_INLINE(bool) filesystem_iterator_current_is_ignored(
+ filesystem_iterator *iter)
+{
+ if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED)
+ filesystem_iterator_update_ignored(iter);
+
+ return (iter->current_is_ignored == GIT_IGNORE_TRUE);
+}
+
+bool git_iterator_current_is_ignored(git_iterator *i)
+{
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ return filesystem_iterator_current_is_ignored((filesystem_iterator *)i);
+}
+
+bool git_iterator_current_tree_is_ignored(git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *frame;
+
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ frame = filesystem_iterator_current_frame(iter);
+ return (frame->is_ignored == GIT_IGNORE_TRUE);
+}
+
+static int filesystem_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *current_frame;
+ filesystem_iterator_entry *current_entry;
+ const git_index_entry *entry = NULL;
+ const char *base;
+ int error = 0;
+
+ *out = NULL;
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+
+ assert(iterator__has_been_accessed(i));
+
+ current_frame = filesystem_iterator_current_frame(iter);
+ assert(current_frame);
+ current_entry = filesystem_iterator_current_entry(current_frame);
+ assert(current_entry);
+
+ if ((error = git_iterator_current(&entry, i)) < 0)
+ return error;
+
+ if (!S_ISDIR(entry->mode)) {
+ if (filesystem_iterator_current_is_ignored(iter))
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+
+ return filesystem_iterator_advance(out, i);
+ }
+
+ git_buf_clear(&iter->tmp_buf);
+ if ((error = git_buf_puts(&iter->tmp_buf, entry->path)) < 0)
+ return error;
+
+ base = iter->tmp_buf.ptr;
+
+ /* scan inside the directory looking for files. if we find nothing,
+ * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to
+ * IGNORED. if we find a real actual item, upgrade all the way to NORMAL
+ * and then stop.
+ *
+ * however, if we're here looking for a pathlist item (but are not
+ * actually in the pathlist ourselves) then start at FILTERED instead of
+ * EMPTY. callers then know that this path was not something they asked
+ * about.
+ */
+ *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ?
+ GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY;
+
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
+ if (filesystem_iterator_current_is_ignored(iter)) {
+ /* if we found an explicitly ignored item, then update from
+ * EMPTY to IGNORED
+ */
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ } else if (S_ISDIR(entry->mode)) {
+ error = filesystem_iterator_advance_into(&entry, i);
+
+ if (!error)
+ continue;
+
+ /* this directory disappeared, ignore it */
+ else if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ /* a real error occurred */
+ else
+ break;
+ } else {
+ /* we found a non-ignored item, treat parent as untracked */
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ break;
+ }
+
+ if ((error = git_iterator_advance(&entry, i)) < 0)
+ break;
+ }
+
+ /* wrap up scan back to base directory */
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
+ if ((error = git_iterator_advance(&entry, i)) < 0)
+ break;
+ }
+
+ if (!error)
+ *out = entry;
+
+ return error;
+}
+
+static void filesystem_iterator_clear(filesystem_iterator *iter)
+{
+ while (iter->frames.size)
+ filesystem_iterator_frame_pop(iter);
+
+ git_array_clear(iter->frames);
+ git_ignore__free(&iter->ignores);
+
+ git_buf_free(&iter->tmp_buf);
+
+ iterator_clear(&iter->base);
+}
+
+static int filesystem_iterator_init(filesystem_iterator *iter)
+{
+ int error;
+
+ if (iterator__honor_ignores(&iter->base) &&
+ (error = git_ignore__for_path(iter->base.repo,
+ ".gitignore", &iter->ignores)) < 0)
+ return error;
+
+ if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0)
+ return error;
+
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ return 0;
+}
+
+static int filesystem_iterator_reset(git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+
+ filesystem_iterator_clear(iter);
+ return filesystem_iterator_init(iter);
+}
+
+static void filesystem_iterator_free(git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ git__free(iter->root);
+ git_buf_free(&iter->current_path);
+ git_tree_free(iter->tree);
+ if (iter->index)
+ git_index_snapshot_release(&iter->index_snapshot, iter->index);
+ filesystem_iterator_clear(iter);
+}
+
+static int iterator_for_filesystem(
+ git_iterator **out,
+ git_repository *repo,
+ const char *root,
+ git_index *index,
+ git_tree *tree,
+ git_iterator_type_t type,
+ git_iterator_options *options)
+{
+ filesystem_iterator *iter;
+ size_t root_len;
+ int error;
+
+ static git_iterator_callbacks callbacks = {
+ filesystem_iterator_current,
+ filesystem_iterator_advance,
+ filesystem_iterator_advance_into,
+ filesystem_iterator_advance_over,
+ filesystem_iterator_reset,
+ filesystem_iterator_free
+ };
+
+ *out = NULL;
+
+ if (root == NULL)
+ return git_iterator_for_nothing(out, options);
+
+ iter = git__calloc(1, sizeof(filesystem_iterator));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->base.type = type;
+ iter->base.cb = &callbacks;
+
+ root_len = strlen(root);
+
+ iter->root = git__malloc(root_len+2);
+ GITERR_CHECK_ALLOC(iter->root);
+
+ memcpy(iter->root, root, root_len);
+
+ if (root_len == 0 || root[root_len-1] != '/') {
+ iter->root[root_len] = '/';
+ root_len++;
+ }
+ iter->root[root_len] = '\0';
+ iter->root_len = root_len;
+
+ if ((error = git_buf_puts(&iter->current_path, iter->root)) < 0)
+ goto on_error;
+
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0)
+ goto on_error;
+
+ if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0)
+ goto on_error;
+
+ if (index &&
+ (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0)
+ goto on_error;
+
+ iter->index = index;
+ iter->dirload_flags =
+ (iterator__ignore_case(&iter->base) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
+ (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ?
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
+
+ if ((error = filesystem_iterator_init(iter)) < 0)
+ goto on_error;
+
+ *out = &iter->base;
+ return 0;
+
+on_error:
+ git_iterator_free(&iter->base);
+ return error;
+}
+
+int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_options *options)
+{
+ return iterator_for_filesystem(out,
+ NULL, root, NULL, NULL, GIT_ITERATOR_TYPE_FS, options);
+}
+
+int git_iterator_for_workdir_ext(
+ git_iterator **out,
+ git_repository *repo,
+ const char *repo_workdir,
+ git_index *index,
+ git_tree *tree,
+ git_iterator_options *given_opts)
+{
+ git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT;
+
+ if (!repo_workdir) {
+ if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
+ return GIT_EBAREREPO;
+
+ repo_workdir = git_repository_workdir(repo);
+ }
+
+ /* upgrade to a workdir iterator, adding necessary internal flags */
+ if (given_opts)
+ memcpy(&options, given_opts, sizeof(git_iterator_options));
+
+ options.flags |= GIT_ITERATOR_HONOR_IGNORES |
+ GIT_ITERATOR_IGNORE_DOT_GIT;
+
+ return iterator_for_filesystem(out,
+ repo, repo_workdir, index, tree, GIT_ITERATOR_TYPE_WORKDIR, &options);
+}
+
+
+/* Index iterator */
+
+
+typedef struct {
+ git_iterator base;
+ git_vector entries;
+ size_t next_idx;
+
+ /* the pseudotree entry */
+ git_index_entry tree_entry;
+ git_buf tree_buf;
+ bool skip_tree;
+
+ const git_index_entry *entry;
+} index_iterator;
+
+static int index_iterator_current(
+ const git_index_entry **out, git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
+
+ if (iter->entry == NULL) {
+ *out = NULL;
+ return GIT_ITEROVER;
+ }
+
+ *out = iter->entry;
+ return 0;
+}
+
+static bool index_iterator_create_pseudotree(
+ const git_index_entry **out,
+ index_iterator *iter,
+ const char *path)
+{
+ const char *prev_path, *relative_path, *dirsep;
+ size_t common_len;
+
+ prev_path = iter->entry ? iter->entry->path : "";
+
+ /* determine if the new path is in a different directory from the old */
+ common_len = git_path_common_dirlen(prev_path, path);
+ relative_path = path + common_len;
+
+ if ((dirsep = strchr(relative_path, '/')) == NULL)
+ return false;
+
+ git_buf_clear(&iter->tree_buf);
+ git_buf_put(&iter->tree_buf, path, (dirsep - path) + 1);
+
+ iter->tree_entry.mode = GIT_FILEMODE_TREE;
+ iter->tree_entry.path = iter->tree_buf.ptr;
+
+ *out = &iter->tree_entry;
+ return true;
+}
+
+static int index_iterator_skip_pseudotree(index_iterator *iter)
+{
+ assert(iterator__has_been_accessed(&iter->base));
+ assert(S_ISDIR(iter->entry->mode));
+
+ while (true) {
+ const git_index_entry *next_entry = NULL;
+
+ if (++iter->next_idx >= iter->entries.length)
+ return GIT_ITEROVER;
+
+ next_entry = iter->entries.contents[iter->next_idx];
+
+ if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path,
+ iter->tree_buf.size) != 0)
+ break;
+ }
+
+ iter->skip_tree = false;
+ return 0;
+}
+
+static int index_iterator_advance(
+ const git_index_entry **out, git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+ const git_index_entry *entry = NULL;
+ bool is_submodule;
+ int error = 0;
+
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ while (true) {
+ if (iter->next_idx >= iter->entries.length) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* we were not asked to expand this pseudotree. advance over it. */
+ if (iter->skip_tree) {
+ index_iterator_skip_pseudotree(iter);
+ continue;
+ }
+
+ entry = iter->entries.contents[iter->next_idx];
+ is_submodule = S_ISGITLINK(entry->mode);
+
+ if (!iterator_has_started(&iter->base, entry->path, is_submodule)) {
+ iter->next_idx++;
+ continue;
+ }
+
+ if (iterator_has_ended(&iter->base, entry->path)) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* if we have a list of paths we're interested in, examine it */
+ if (!iterator_pathlist_next_is(&iter->base, entry->path)) {
+ iter->next_idx++;
+ continue;
+ }
+
+ /* if this is a conflict, skip it unless we're including conflicts */
+ if (git_index_entry_is_conflict(entry) &&
+ !iterator__include_conflicts(&iter->base)) {
+ iter->next_idx++;
+ continue;
+ }
+
+ /* we've found what will be our next _file_ entry. but if we are
+ * returning trees entries, we may need to return a pseudotree
+ * entry that will contain this. don't advance over this entry,
+ * though, we still need to return it on the next `advance`.
+ */
+ if (iterator__include_trees(&iter->base) &&
+ index_iterator_create_pseudotree(&entry, iter, entry->path)) {
+
+ /* Note whether this pseudo tree should be expanded or not */
+ iter->skip_tree = iterator__dont_autoexpand(&iter->base);
+ break;
+ }
+
+ iter->next_idx++;
+ break;
+ }
+
+ iter->entry = (error == 0) ? entry : NULL;
+
+ if (out)
+ *out = iter->entry;
+
+ return error;
+}
+
+static int index_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+
+ if (! S_ISDIR(iter->tree_entry.mode)) {
+ if (out)
+ *out = NULL;
+
+ return 0;
+ }
+
+ iter->skip_tree = false;
+ return index_iterator_advance(out, i);
+}
+
+static int index_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+ const git_index_entry *entry;
+ int error;
+
+ if ((error = index_iterator_current(&entry, i)) < 0)
+ return error;
+
+ if (S_ISDIR(entry->mode))
+ index_iterator_skip_pseudotree(iter);
+
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ return index_iterator_advance(out, i);
+}
+
+static void index_iterator_clear(index_iterator *iter)
+{
+ iterator_clear(&iter->base);
+}
+
+static int index_iterator_init(index_iterator *iter)
+{
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+ iter->next_idx = 0;
+ iter->skip_tree = false;
+ return 0;
+}
+
+static int index_iterator_reset(git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+
+ index_iterator_clear(iter);
+ return index_iterator_init(iter);
+}
+
+static void index_iterator_free(git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
+
+ git_index_snapshot_release(&iter->entries, iter->base.index);
+ git_buf_free(&iter->tree_buf);
+}
+
+int git_iterator_for_index(
+ git_iterator **out,
+ git_repository *repo,
+ git_index *index,
+ git_iterator_options *options)
+{
+ index_iterator *iter;
+ int error;
+
+ static git_iterator_callbacks callbacks = {
+ index_iterator_current,
+ index_iterator_advance,
+ index_iterator_advance_into,
+ index_iterator_advance_over,
+ index_iterator_reset,
+ index_iterator_free
+ };
+
+ *out = NULL;
+
+ if (index == NULL)
+ return git_iterator_for_nothing(out, options);
+
+ iter = git__calloc(1, sizeof(index_iterator));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->base.type = GIT_ITERATOR_TYPE_INDEX;
+ iter->base.cb = &callbacks;
+
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 ||
+ (error = git_index_snapshot_new(&iter->entries, index)) < 0 ||
+ (error = index_iterator_init(iter)) < 0)
+ goto on_error;
+
+ git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ?
+ git_index_entry_icmp : git_index_entry_cmp);
+ git_vector_sort(&iter->entries);
+
+ *out = &iter->base;
+ return 0;
+
+on_error:
+ git_iterator_free(&iter->base);
+ return error;
+}
+
+
+/* Iterator API */
+
+int git_iterator_reset_range(
+ git_iterator *i, const char *start, const char *end)
+{
+ if (iterator_reset_range(i, start, end) < 0)
+ return -1;
+
+ return i->cb->reset(i);
+}
+
+void git_iterator_set_ignore_case(git_iterator *i, bool ignore_case)
+{
+ assert(!iterator__has_been_accessed(i));
+ iterator_set_ignore_case(i, ignore_case);
+}
+
+void git_iterator_free(git_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ iter->cb->free(iter);
+
+ git_vector_free(&iter->pathlist);
+ git__free(iter->start);
+ git__free(iter->end);
+
+ memset(iter, 0, sizeof(*iter));
+
+ git__free(iter);
+}
+
+int git_iterator_walk(
+ git_iterator **iterators,
+ size_t cnt,
+ git_iterator_walk_cb cb,
+ void *data)
+{
+ const git_index_entry **iterator_item; /* next in each iterator */
+ const git_index_entry **cur_items; /* current path in each iter */
+ const git_index_entry *first_match;
+ size_t i, j;
+ int error = 0;
+
+ iterator_item = git__calloc(cnt, sizeof(git_index_entry *));
+ cur_items = git__calloc(cnt, sizeof(git_index_entry *));
+
+ GITERR_CHECK_ALLOC(iterator_item);
+ GITERR_CHECK_ALLOC(cur_items);
+
+ /* Set up the iterators */
+ for (i = 0; i < cnt; i++) {
+ error = git_iterator_current(&iterator_item[i], iterators[i]);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+
+ while (true) {
+ for (i = 0; i < cnt; i++)
+ cur_items[i] = NULL;
+
+ first_match = NULL;
+
+ /* Find the next path(s) to consume from each iterator */
+ for (i = 0; i < cnt; i++) {
+ if (iterator_item[i] == NULL)
+ continue;
+
+ if (first_match == NULL) {
+ first_match = iterator_item[i];
+ cur_items[i] = iterator_item[i];
+ } else {
+ int path_diff = git_index_entry_cmp(iterator_item[i], first_match);
+
+ if (path_diff < 0) {
+ /* Found an index entry that sorts before the one we're
+ * looking at. Forget that we've seen the other and
+ * look at the other iterators for this path.
+ */
+ for (j = 0; j < i; j++)
+ cur_items[j] = NULL;
+
+ first_match = iterator_item[i];
+ cur_items[i] = iterator_item[i];
+ } else if (path_diff == 0) {
+ cur_items[i] = iterator_item[i];
+ }
+ }
+ }
+
+ if (first_match == NULL)
+ break;
+
+ if ((error = cb(cur_items, data)) != 0)
+ goto done;
+
+ /* Advance each iterator that participated */
+ for (i = 0; i < cnt; i++) {
+ if (cur_items[i] == NULL)
+ continue;
+
+ error = git_iterator_advance(&iterator_item[i], iterators[i]);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+ }
+
+done:
+ git__free((git_index_entry **)iterator_item);
+ git__free((git_index_entry **)cur_items);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_iterator_h__
+#define INCLUDE_iterator_h__
+
+#include "common.h"
+#include "git2/index.h"
+#include "vector.h"
+#include "buffer.h"
+#include "ignore.h"
+
+typedef struct git_iterator git_iterator;
+
+typedef enum {
+ GIT_ITERATOR_TYPE_EMPTY = 0,
+ GIT_ITERATOR_TYPE_TREE = 1,
+ GIT_ITERATOR_TYPE_INDEX = 2,
+ GIT_ITERATOR_TYPE_WORKDIR = 3,
+ GIT_ITERATOR_TYPE_FS = 4,
+} git_iterator_type_t;
+
+typedef enum {
+ /** ignore case for entry sort order */
+ GIT_ITERATOR_IGNORE_CASE = (1u << 0),
+ /** force case sensitivity for entry sort order */
+ GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1),
+ /** return tree items in addition to blob items */
+ GIT_ITERATOR_INCLUDE_TREES = (1u << 2),
+ /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
+ GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3),
+ /** convert precomposed unicode to decomposed unicode */
+ GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4),
+ /** never convert precomposed unicode to decomposed unicode */
+ GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5),
+ /** include conflicts */
+ GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6),
+} git_iterator_flag_t;
+
+typedef enum {
+ GIT_ITERATOR_STATUS_NORMAL = 0,
+ GIT_ITERATOR_STATUS_IGNORED = 1,
+ GIT_ITERATOR_STATUS_EMPTY = 2,
+ GIT_ITERATOR_STATUS_FILTERED = 3
+} git_iterator_status_t;
+
+typedef struct {
+ const char *start;
+ const char *end;
+
+ /* paths to include in the iterator (literal). if set, any paths not
+ * listed here will be excluded from iteration.
+ */
+ git_strarray pathlist;
+
+ /* flags, from above */
+ unsigned int flags;
+} git_iterator_options;
+
+#define GIT_ITERATOR_OPTIONS_INIT {0}
+
+typedef struct {
+ int (*current)(const git_index_entry **, git_iterator *);
+ int (*advance)(const git_index_entry **, git_iterator *);
+ int (*advance_into)(const git_index_entry **, git_iterator *);
+ int (*advance_over)(
+ const git_index_entry **, git_iterator_status_t *, git_iterator *);
+ int (*reset)(git_iterator *);
+ void (*free)(git_iterator *);
+} git_iterator_callbacks;
+
+struct git_iterator {
+ git_iterator_type_t type;
+ git_iterator_callbacks *cb;
+
+ git_repository *repo;
+ git_index *index;
+
+ char *start;
+ size_t start_len;
+
+ char *end;
+ size_t end_len;
+
+ bool started;
+ bool ended;
+ git_vector pathlist;
+ size_t pathlist_walk_idx;
+ int (*strcomp)(const char *a, const char *b);
+ int (*strncomp)(const char *a, const char *b, size_t n);
+ int (*prefixcomp)(const char *str, const char *prefix);
+ int (*entry_srch)(const void *key, const void *array_member);
+ size_t stat_calls;
+ unsigned int flags;
+};
+
+extern int git_iterator_for_nothing(
+ git_iterator **out,
+ git_iterator_options *options);
+
+/* tree iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+extern int git_iterator_for_tree(
+ git_iterator **out,
+ git_tree *tree,
+ git_iterator_options *options);
+
+/* index iterators will take the ignore_case value from the index; the
+ * ignore_case flags are not used
+ */
+extern int git_iterator_for_index(
+ git_iterator **out,
+ git_repository *repo,
+ git_index *index,
+ git_iterator_options *options);
+
+extern int git_iterator_for_workdir_ext(
+ git_iterator **out,
+ git_repository *repo,
+ const char *repo_workdir,
+ git_index *index,
+ git_tree *tree,
+ git_iterator_options *options);
+
+/* workdir iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+GIT_INLINE(int) git_iterator_for_workdir(
+ git_iterator **out,
+ git_repository *repo,
+ git_index *index,
+ git_tree *tree,
+ git_iterator_options *options)
+{
+ return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options);
+}
+
+/* for filesystem iterators, you have to explicitly pass in the ignore_case
+ * behavior that you desire
+ */
+extern int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_options *options);
+
+extern void git_iterator_free(git_iterator *iter);
+
+/* Return a git_index_entry structure for the current value the iterator
+ * is looking at or NULL if the iterator is at the end.
+ *
+ * The entry may noy be fully populated. Tree iterators will only have a
+ * value mode, OID, and path. Workdir iterators will not have an OID (but
+ * you can use `git_iterator_current_oid()` to calculate it on demand).
+ *
+ * You do not need to free the entry. It is still "owned" by the iterator.
+ * Once you call `git_iterator_advance()` then the old entry is no longer
+ * guaranteed to be valid - it may be freed or just overwritten in place.
+ */
+GIT_INLINE(int) git_iterator_current(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->current(entry, iter);
+}
+
+/**
+ * Advance to the next item for the iterator.
+ *
+ * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If
+ * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree
+ * item will skip over all the items under that tree.
+ */
+GIT_INLINE(int) git_iterator_advance(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->advance(entry, iter);
+}
+
+/**
+ * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).
+ *
+ * git_iterator_advance() steps through all items being iterated over
+ * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),
+ * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next
+ * sibling of a tree instead of going to the first child of the tree. In
+ * that case, use this function to advance to the first child of the tree.
+ *
+ * If the current item is not a tree, this is a no-op.
+ *
+ * For filesystem and working directory iterators, a tree (i.e. directory)
+ * can be empty. In that case, this function returns GIT_ENOTFOUND and
+ * does not advance. That can't happen for tree and index iterators.
+ */
+GIT_INLINE(int) git_iterator_advance_into(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->advance_into(entry, iter);
+}
+
+/* Advance over a directory and check if it contains no files or just
+ * ignored files.
+ *
+ * In a tree or the index, all directories will contain files, but in the
+ * working directory it is possible to have an empty directory tree or a
+ * tree that only contains ignored files. Many Git operations treat these
+ * cases specially. This advances over a directory (presumably an
+ * untracked directory) but checks during the scan if there are any files
+ * and any non-ignored files.
+ */
+GIT_INLINE(int) git_iterator_advance_over(
+ const git_index_entry **entry,
+ git_iterator_status_t *status,
+ git_iterator *iter)
+{
+ return iter->cb->advance_over(entry, status, iter);
+}
+
+/**
+ * Go back to the start of the iteration.
+ */
+GIT_INLINE(int) git_iterator_reset(git_iterator *iter)
+{
+ return iter->cb->reset(iter);
+}
+
+/**
+ * Go back to the start of the iteration after updating the `start` and
+ * `end` pathname boundaries of the iteration.
+ */
+extern int git_iterator_reset_range(
+ git_iterator *iter, const char *start, const char *end);
+
+GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
+{
+ return iter->type;
+}
+
+GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)
+{
+ return iter->repo;
+}
+
+GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter)
+{
+ return iter->index;
+}
+
+GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)
+{
+ return iter->flags;
+}
+
+GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
+{
+ return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
+}
+
+extern void git_iterator_set_ignore_case(
+ git_iterator *iter, bool ignore_case);
+
+extern int git_iterator_current_tree_entry(
+ const git_tree_entry **entry_out, git_iterator *iter);
+
+extern int git_iterator_current_parent_tree(
+ const git_tree **tree_out, git_iterator *iter, size_t depth);
+
+extern bool git_iterator_current_is_ignored(git_iterator *iter);
+
+extern bool git_iterator_current_tree_is_ignored(git_iterator *iter);
+
+/**
+ * Get full path of the current item from a workdir iterator. This will
+ * return NULL for a non-workdir iterator. The git_buf is still owned by
+ * the iterator; this is exposed just for efficiency.
+ */
+extern int git_iterator_current_workdir_path(
+ git_buf **path, git_iterator *iter);
+
+/**
+ * Retrieve the index stored in the iterator.
+ *
+ * Only implemented for the workdir and index iterators.
+ */
+extern git_index *git_iterator_index(git_iterator *iter);
+
+typedef int (*git_iterator_walk_cb)(
+ const git_index_entry **entries,
+ void *data);
+
+/**
+ * Walk the given iterators in lock-step. The given callback will be
+ * called for each unique path, with the index entry in each iterator
+ * (or NULL if the given iterator does not contain that path).
+ */
+extern int git_iterator_walk(
+ git_iterator **iterators,
+ size_t cnt,
+ git_iterator_walk_cb cb,
+ void *data);
+
+#endif
--- /dev/null
+/* The MIT License
+
+ Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ An example:
+
+#include "khash.h"
+KHASH_MAP_INIT_INT(32, char)
+int main() {
+ int ret, is_missing;
+ khiter_t k;
+ khash_t(32) *h = kh_init(32);
+ k = kh_put(32, h, 5, &ret);
+ kh_value(h, k) = 10;
+ k = kh_get(32, h, 10);
+ is_missing = (k == kh_end(h));
+ k = kh_get(32, h, 5);
+ kh_del(32, h, k);
+ for (k = kh_begin(h); k != kh_end(h); ++k)
+ if (kh_exist(h, k)) kh_value(h, k) = 1;
+ kh_destroy(32, h);
+ return 0;
+}
+*/
+
+/*
+ 2013-05-02 (0.2.8):
+
+ * Use quadratic probing. When the capacity is power of 2, stepping function
+ i*(i+1)/2 guarantees to traverse each bucket. It is better than double
+ hashing on cache performance and is more robust than linear probing.
+
+ In theory, double hashing should be more robust than quadratic probing.
+ However, my implementation is probably not for large hash tables, because
+ the second hash function is closely tied to the first hash function,
+ which reduce the effectiveness of double hashing.
+
+ Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
+
+ 2011-12-29 (0.2.7):
+
+ * Minor code clean up; no actual effect.
+
+ 2011-09-16 (0.2.6):
+
+ * The capacity is a power of 2. This seems to dramatically improve the
+ speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
+
+ - http://code.google.com/p/ulib/
+ - http://nothings.org/computer/judy/
+
+ * Allow to optionally use linear probing which usually has better
+ performance for random input. Double hashing is still the default as it
+ is more robust to certain non-random input.
+
+ * Added Wang's integer hash function (not used by default). This hash
+ function is more robust to certain non-random input.
+
+ 2011-02-14 (0.2.5):
+
+ * Allow to declare global functions.
+
+ 2009-09-26 (0.2.4):
+
+ * Improve portability
+
+ 2008-09-19 (0.2.3):
+
+ * Corrected the example
+ * Improved interfaces
+
+ 2008-09-11 (0.2.2):
+
+ * Improved speed a little in kh_put()
+
+ 2008-09-10 (0.2.1):
+
+ * Added kh_clear()
+ * Fixed a compiling error
+
+ 2008-09-02 (0.2.0):
+
+ * Changed to token concatenation which increases flexibility.
+
+ 2008-08-31 (0.1.2):
+
+ * Fixed a bug in kh_get(), which has not been tested previously.
+
+ 2008-08-31 (0.1.1):
+
+ * Added destructor
+*/
+
+
+#ifndef __AC_KHASH_H
+#define __AC_KHASH_H
+
+/*!
+ @header
+
+ Generic hash table library.
+ */
+
+#define AC_VERSION_KHASH_H "0.2.8"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* compiler specific configuration */
+
+#if UINT_MAX == 0xffffffffu
+typedef unsigned int khint32_t;
+#elif ULONG_MAX == 0xffffffffu
+typedef unsigned long khint32_t;
+#endif
+
+#if ULONG_MAX == ULLONG_MAX
+typedef unsigned long khint64_t;
+#else
+typedef unsigned long long khint64_t;
+#endif
+
+#ifndef kh_inline
+#ifdef _MSC_VER
+#define kh_inline __inline
+#else
+#define kh_inline inline
+#endif
+#endif /* kh_inline */
+
+typedef khint32_t khint_t;
+typedef khint_t khiter_t;
+
+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
+
+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+
+#ifndef kroundup32
+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+#endif
+
+#ifndef kcalloc
+#define kcalloc(N,Z) calloc(N,Z)
+#endif
+#ifndef kmalloc
+#define kmalloc(Z) malloc(Z)
+#endif
+#ifndef krealloc
+#define krealloc(P,Z) realloc(P,Z)
+#endif
+#ifndef kreallocarray
+#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z)))
+#endif
+#ifndef kfree
+#define kfree(P) free(P)
+#endif
+
+static const double __ac_HASH_UPPER = 0.77;
+
+#define __KHASH_TYPE(name, khkey_t, khval_t) \
+ typedef struct kh_##name##_s { \
+ khint_t n_buckets, size, n_occupied, upper_bound; \
+ khint32_t *flags; \
+ khkey_t *keys; \
+ khval_t *vals; \
+ } kh_##name##_t;
+
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t *kh_init_##name(void); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kfree((void *)h->keys); kfree(h->flags); \
+ kfree((void *)h->vals); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t k, i, last, mask, step = 0; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ i = (i + (++step)) & mask; \
+ if (i == last) return h->n_buckets; \
+ } \
+ return __ac_iseither(h->flags, i)? h->n_buckets : i; \
+ } else return 0; \
+ } \
+ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) new_n_buckets = 4; \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
+ else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \
+ if (!new_flags) return -1; \
+ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \
+ if (!new_keys) { kfree(new_flags); return -1; } \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \
+ if (!new_vals) { kfree(new_flags); return -1; } \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) val = h->vals[j]; \
+ __ac_set_isdel_true(h->flags, j); \
+ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
+ khint_t k, i, step = 0; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
+ __ac_set_isempty_false(new_flags, i); \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
+ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
+ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
+ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) h->vals[i] = val; \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \
+ if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ return 0; \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size<<1)) { \
+ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
+ { \
+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
+ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
+ else { \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) site = i; \
+ i = (i + (++step)) & mask; \
+ if (i == last) { x = site; break; } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
+ else x = i; \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; ++h->n_occupied; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; \
+ *ret = 2; \
+ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
+
+#define KHASH_DECLARE(name, khkey_t, khval_t) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_PROTOTYPES(name, khkey_t, khval_t)
+
+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+/* --- BEGIN OF HASH FUNCTIONS --- */
+
+/*! @function
+ @abstract Integer hash function
+ @param key The integer [khint32_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int_hash_func(key) (khint32_t)(key)
+/*! @function
+ @abstract Integer comparison function
+ */
+#define kh_int_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract 64-bit integer hash function
+ @param key The integer [khint64_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
+/*! @function
+ @abstract 64-bit integer comparison function
+ */
+#define kh_int64_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract const char* hash function
+ @param s Pointer to a null terminated string
+ @return The hash value
+ */
+static kh_inline khint_t __ac_X31_hash_string(const char *s)
+{
+ khint_t h = (khint_t)*s;
+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
+ return h;
+}
+/*! @function
+ @abstract Another interface to const char* hash function
+ @param key Pointer to a null terminated string [const char*]
+ @return The hash value [khint_t]
+ */
+#define kh_str_hash_func(key) __ac_X31_hash_string(key)
+/*! @function
+ @abstract Const char* comparison function
+ */
+#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
+
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
+{
+ key += ~(key << 15);
+ key ^= (key >> 10);
+ key += (key << 3);
+ key ^= (key >> 6);
+ key += ~(key << 11);
+ key ^= (key >> 16);
+ return key;
+}
+#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
+
+/* --- END OF HASH FUNCTIONS --- */
+
+/* Other convenient macros... */
+
+/*!
+ @abstract Type of the hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define khash_t(name) kh_##name##_t
+
+/*! @function
+ @abstract Initiate a hash table.
+ @param name Name of the hash table [symbol]
+ @return Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_init(name) kh_init_##name()
+
+/*! @function
+ @abstract Destroy a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_destroy(name, h) kh_destroy_##name(h)
+
+/*! @function
+ @abstract Reset a hash table without deallocating memory.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_clear(name, h) kh_clear_##name(h)
+
+/*! @function
+ @abstract Resize a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param s New size [khint_t]
+ */
+#define kh_resize(name, h, s) kh_resize_##name(h, s)
+
+/*! @function
+ @abstract Insert a key to the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @param r Extra return code: -1 if the operation failed;
+ 0 if the key is present in the hash table;
+ 1 if the bucket is empty (never used); 2 if the element in
+ the bucket has been deleted [int*]
+ @return Iterator to the inserted element [khint_t]
+ */
+#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
+
+/*! @function
+ @abstract Retrieve a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
+ */
+#define kh_get(name, h, k) kh_get_##name(h, k)
+
+/*! @function
+ @abstract Remove a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Iterator to the element to be deleted [khint_t]
+ */
+#define kh_del(name, h, k) kh_del_##name(h, k)
+
+/*! @function
+ @abstract Test whether a bucket contains data.
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return 1 if containing data; 0 otherwise [int]
+ */
+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
+
+/*! @function
+ @abstract Get key given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Key [type of keys]
+ */
+#define kh_key(h, x) ((h)->keys[x])
+
+/*! @function
+ @abstract Get value given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Value [type of values]
+ @discussion For hash sets, calling this results in segfault.
+ */
+#define kh_val(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Alias of kh_val()
+ */
+#define kh_value(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Get the start iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The start iterator [khint_t]
+ */
+#define kh_begin(h) (khint_t)(0)
+
+/*! @function
+ @abstract Get the end iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The end iterator [khint_t]
+ */
+#define kh_end(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Get the number of elements in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of elements in the hash table [khint_t]
+ */
+#define kh_size(h) ((h)->size)
+
+/*! @function
+ @abstract Get the number of buckets in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of buckets in the hash table [khint_t]
+ */
+#define kh_n_buckets(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Iterate over the entries in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which key will be assigned
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (kvar) = kh_key(h,__i); \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/*! @function
+ @abstract Iterate over the values in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_value(h, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/* More conenient interfaces */
+
+/*! @function
+ @abstract Instantiate a hash set containing integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT(name) \
+ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT(name, khval_t) \
+ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT64(name) \
+ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT64(name, khval_t) \
+ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+typedef const char *kh_cstr_t;
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_STR(name) \
+ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#endif /* __AC_KHASH_H */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_map_h__
+#define INCLUDE_map_h__
+
+#include "common.h"
+
+
+/* p_mmap() prot values */
+#define GIT_PROT_NONE 0x0
+#define GIT_PROT_READ 0x1
+#define GIT_PROT_WRITE 0x2
+#define GIT_PROT_EXEC 0x4
+
+/* git__mmmap() flags values */
+#define GIT_MAP_FILE 0
+#define GIT_MAP_SHARED 1
+#define GIT_MAP_PRIVATE 2
+#define GIT_MAP_TYPE 0xf
+#define GIT_MAP_FIXED 0x10
+
+#ifdef __amigaos4__
+#define MAP_FAILED 0
+#endif
+
+typedef struct { /* memory mapped buffer */
+ void *data; /* data bytes */
+ size_t len; /* data length */
+#ifdef GIT_WIN32
+ HANDLE fmh; /* file mapping handle */
+#endif
+} git_map;
+
+#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \
+ assert(out != NULL && len > 0); \
+ assert((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \
+ assert((flags & GIT_MAP_FIXED) == 0); } while (0)
+
+extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset);
+extern int p_munmap(git_map *map);
+
+#endif /* INCLUDE_map_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
+#include "repository.h"
+#include "revwalk.h"
+#include "commit_list.h"
+#include "merge.h"
+#include "path.h"
+#include "refs.h"
+#include "object.h"
+#include "iterator.h"
+#include "refs.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "diff_tform.h"
+#include "checkout.h"
+#include "tree.h"
+#include "blob.h"
+#include "oid.h"
+#include "index.h"
+#include "filebuf.h"
+#include "config.h"
+#include "oidarray.h"
+#include "annotated_commit.h"
+#include "commit.h"
+#include "oidarray.h"
+#include "merge_driver.h"
+
+#include "git2/types.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/commit.h"
+#include "git2/merge.h"
+#include "git2/refs.h"
+#include "git2/reset.h"
+#include "git2/checkout.h"
+#include "git2/signature.h"
+#include "git2/config.h"
+#include "git2/tree.h"
+#include "git2/oidarray.h"
+#include "git2/annotated_commit.h"
+#include "git2/sys/index.h"
+#include "git2/sys/hashsig.h"
+
+#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
+#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode)
+
+
+typedef enum {
+ TREE_IDX_ANCESTOR = 0,
+ TREE_IDX_OURS = 1,
+ TREE_IDX_THEIRS = 2
+} merge_tree_index_t;
+
+/* Tracks D/F conflicts */
+struct merge_diff_df_data {
+ const char *df_path;
+ const char *prev_path;
+ git_merge_diff *prev_conflict;
+};
+
+/* Merge base computation */
+
+int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_revwalk *walk = NULL;
+ git_vector list;
+ git_commit_list *result = NULL;
+ git_commit_list_node *commit;
+ int error = -1;
+ unsigned int i;
+
+ if (length < 2) {
+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %" PRIuZ ".", length);
+ return -1;
+ }
+
+ if (git_vector_init(&list, length - 1, NULL) < 0)
+ return -1;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ goto on_error;
+
+ for (i = 1; i < length; i++) {
+ commit = git_revwalk__commit_lookup(walk, &input_array[i]);
+ if (commit == NULL)
+ goto on_error;
+
+ git_vector_insert(&list, commit);
+ }
+
+ commit = git_revwalk__commit_lookup(walk, &input_array[0]);
+ if (commit == NULL)
+ goto on_error;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto on_error;
+
+ if (!result) {
+ giterr_set(GITERR_MERGE, "No merge base found");
+ error = GIT_ENOTFOUND;
+ goto on_error;
+ }
+
+ *out = result;
+ *walk_out = walk;
+
+ git_vector_free(&list);
+ return 0;
+
+on_error:
+ git_vector_free(&list);
+ git_revwalk_free(walk);
+ return error;
+}
+
+int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_revwalk *walk;
+ git_commit_list *result = NULL;
+ int error = 0;
+
+ assert(out && repo && input_array);
+
+ if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0)
+ return error;
+
+ git_oid_cpy(out, &result->item->oid);
+
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
+}
+
+int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_revwalk *walk;
+ git_commit_list *list, *result = NULL;
+ int error = 0;
+ git_array_oid_t array;
+
+ assert(out && repo && input_array);
+
+ if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0)
+ return error;
+
+ git_array_init(array);
+
+ list = result;
+ while (list) {
+ git_oid *id = git_array_alloc(array);
+ if (id == NULL) {
+ error = -1;
+ goto cleanup;
+ }
+
+ git_oid_cpy(id, &list->item->oid);
+ list = list->next;
+ }
+
+ git_oidarray__from_array(out, &array);
+
+cleanup:
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return error;
+}
+
+int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_oid result;
+ unsigned int i;
+ int error = -1;
+
+ assert(out && repo && input_array);
+
+ if (length < 2) {
+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %" PRIuZ ".", length);
+ return -1;
+ }
+
+ result = input_array[0];
+ for (i = 1; i < length; i++) {
+ error = git_merge_base(&result, repo, &result, &input_array[i]);
+ if (error < 0)
+ return error;
+ }
+
+ *out = result;
+
+ return 0;
+}
+
+static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ git_revwalk *walk;
+ git_vector list;
+ git_commit_list *result = NULL;
+ git_commit_list_node *commit;
+ void *contents[1];
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit = git_revwalk__commit_lookup(walk, two);
+ if (commit == NULL)
+ goto on_error;
+
+ /* This is just one value, so we can do it on the stack */
+ memset(&list, 0x0, sizeof(git_vector));
+ contents[0] = commit;
+ list.length = 1;
+ list.contents = contents;
+
+ commit = git_revwalk__commit_lookup(walk, one);
+ if (commit == NULL)
+ goto on_error;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto on_error;
+
+ if (!result) {
+ git_revwalk_free(walk);
+ giterr_set(GITERR_MERGE, "No merge base found");
+ return GIT_ENOTFOUND;
+ }
+
+ *out = result;
+ *walk_out = walk;
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+
+}
+
+int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ int error;
+ git_revwalk *walk;
+ git_commit_list *result;
+
+ if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+ return error;
+
+ git_oid_cpy(out, &result->item->oid);
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
+}
+
+int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ int error;
+ git_revwalk *walk;
+ git_commit_list *result, *list;
+ git_array_oid_t array;
+
+ git_array_init(array);
+
+ if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+ return error;
+
+ list = result;
+ while (list) {
+ git_oid *id = git_array_alloc(array);
+ if (id == NULL)
+ goto on_error;
+
+ git_oid_cpy(id, &list->item->oid);
+ list = list->next;
+ }
+
+ git_oidarray__from_array(out, &array);
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+ return -1;
+}
+
+static int interesting(git_pqueue *list)
+{
+ size_t i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void clear_commit_marks_1(git_commit_list **plist,
+ git_commit_list_node *commit, unsigned int mark)
+{
+ while (commit) {
+ unsigned int i;
+
+ if (!(mark & commit->flags))
+ return;
+
+ commit->flags &= ~mark;
+
+ for (i = 1; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ git_commit_list_insert(p, plist);
+ }
+
+ commit = commit->out_degree ? commit->parents[0] : NULL;
+ }
+}
+
+static void clear_commit_marks_many(git_vector *commits, unsigned int mark)
+{
+ git_commit_list *list = NULL;
+ git_commit_list_node *c;
+ unsigned int i;
+
+ git_vector_foreach(commits, i, c) {
+ git_commit_list_insert(c, &list);
+ }
+
+ while (list)
+ clear_commit_marks_1(&list, git_commit_list_pop(&list), mark);
+}
+
+static void clear_commit_marks(git_commit_list_node *commit, unsigned int mark)
+{
+ git_commit_list *list = NULL;
+ git_commit_list_insert(commit, &list);
+ while (list)
+ clear_commit_marks_1(&list, git_commit_list_pop(&list), mark);
+}
+
+static int paint_down_to_common(
+ git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos)
+{
+ git_pqueue list;
+ git_commit_list *result = NULL;
+ git_commit_list_node *two;
+
+ int error;
+ unsigned int i;
+
+ if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ return -1;
+
+ git_vector_foreach(twos, i, two) {
+ if (git_commit_list_parse(walk, two) < 0)
+ return -1;
+
+ two->flags |= PARENT2;
+
+ if (git_pqueue_insert(&list, two) < 0)
+ return -1;
+ }
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list)) {
+ git_commit_list_node *commit = git_pqueue_pop(&list);
+ int flags;
+
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT)) {
+ commit->flags |= RESULT;
+ if (git_commit_list_insert(commit, &result) == NULL)
+ return -1;
+ }
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ return -1;
+ }
+ }
+
+ git_pqueue_free(&list);
+ *out = result;
+ return 0;
+}
+
+static int remove_redundant(git_revwalk *walk, git_vector *commits)
+{
+ git_vector work = GIT_VECTOR_INIT;
+ unsigned char *redundant;
+ unsigned int *filled_index;
+ unsigned int i, j;
+ int error = 0;
+
+ redundant = git__calloc(commits->length, 1);
+ GITERR_CHECK_ALLOC(redundant);
+ filled_index = git__calloc((commits->length - 1), sizeof(unsigned int));
+ GITERR_CHECK_ALLOC(filled_index);
+
+ for (i = 0; i < commits->length; ++i) {
+ if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0)
+ goto done;
+ }
+
+ for (i = 0; i < commits->length; ++i) {
+ git_commit_list *common = NULL;
+ git_commit_list_node *commit = commits->contents[i];
+
+ if (redundant[i])
+ continue;
+
+ git_vector_clear(&work);
+
+ for (j = 0; j < commits->length; j++) {
+ if (i == j || redundant[j])
+ continue;
+
+ filled_index[work.length] = j;
+ if ((error = git_vector_insert(&work, commits->contents[j])) < 0)
+ goto done;
+ }
+
+ error = paint_down_to_common(&common, walk, commit, &work);
+ if (error < 0)
+ goto done;
+
+ if (commit->flags & PARENT2)
+ redundant[i] = 1;
+
+ for (j = 0; j < work.length; j++) {
+ git_commit_list_node *w = work.contents[j];
+ if (w->flags & PARENT1)
+ redundant[filled_index[j]] = 1;
+ }
+
+ clear_commit_marks(commit, ALL_FLAGS);
+ clear_commit_marks_many(&work, ALL_FLAGS);
+
+ git_commit_list_free(&common);
+ }
+
+ for (i = 0; i < commits->length; ++i) {
+ if (redundant[i])
+ commits->contents[i] = NULL;
+ }
+
+done:
+ git__free(redundant);
+ git__free(filled_index);
+ git_vector_free(&work);
+ return error;
+}
+
+int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos)
+{
+ int error;
+ unsigned int i;
+ git_commit_list_node *two;
+ git_commit_list *result = NULL, *tmp = NULL;
+
+ /* If there's only the one commit, there can be no merge bases */
+ if (twos->length == 0) {
+ *out = NULL;
+ return 0;
+ }
+
+ /* if the commit is repeated, we have a our merge base already */
+ git_vector_foreach(twos, i, two) {
+ if (one == two)
+ return git_commit_list_insert(one, out) ? 0 : -1;
+ }
+
+ if (git_commit_list_parse(walk, one) < 0)
+ return -1;
+
+ error = paint_down_to_common(&result, walk, one, twos);
+ if (error < 0)
+ return error;
+
+ /* filter out any stale commits in the results */
+ tmp = result;
+ result = NULL;
+
+ while (tmp) {
+ git_commit_list_node *c = git_commit_list_pop(&tmp);
+ if (!(c->flags & STALE))
+ if (git_commit_list_insert_by_date(c, &result) == NULL)
+ return -1;
+ }
+
+ /*
+ * more than one merge base -- see if there are redundant merge
+ * bases and remove them
+ */
+ if (result && result->next) {
+ git_vector redundant = GIT_VECTOR_INIT;
+
+ while (result)
+ git_vector_insert(&redundant, git_commit_list_pop(&result));
+
+ clear_commit_marks(one, ALL_FLAGS);
+ clear_commit_marks_many(twos, ALL_FLAGS);
+
+ if ((error = remove_redundant(walk, &redundant)) < 0) {
+ git_vector_free(&redundant);
+ return error;
+ }
+
+ git_vector_foreach(&redundant, i, two) {
+ if (two != NULL)
+ git_commit_list_insert_by_date(two, &result);
+ }
+
+ git_vector_free(&redundant);
+ }
+
+ *out = result;
+ return 0;
+}
+
+int git_repository_mergehead_foreach(
+ git_repository *repo,
+ git_repository_mergehead_foreach_cb cb,
+ void *payload)
+{
+ git_buf merge_head_path = GIT_BUF_INIT, merge_head_file = GIT_BUF_INIT;
+ char *buffer, *line;
+ size_t line_num = 1;
+ git_oid oid;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository,
+ GIT_MERGE_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_futils_readbuffer(&merge_head_file,
+ git_buf_cstr(&merge_head_path))) < 0)
+ goto cleanup;
+
+ buffer = merge_head_file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ if (strlen(line) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_INVALID, "Unable to parse OID - invalid length");
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_oid_fromstr(&oid, line)) < 0)
+ goto cleanup;
+
+ if ((error = cb(&oid, payload)) != 0) {
+ giterr_set_after_callback(error);
+ goto cleanup;
+ }
+
+ ++line_num;
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_MERGE, "No EOL at line %"PRIuZ, line_num);
+ error = -1;
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&merge_head_path);
+ git_buf_free(&merge_head_file);
+
+ return error;
+}
+
+GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b)
+{
+ int value = 0;
+
+ if (a->path == NULL)
+ return (b->path == NULL) ? 0 : 1;
+
+ if ((value = a->mode - b->mode) == 0 &&
+ (value = git_oid__cmp(&a->id, &b->id)) == 0)
+ value = strcmp(a->path, b->path);
+
+ return value;
+}
+
+/* Conflict resolution */
+
+static int merge_conflict_resolve_trivial(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed, ours_theirs_differ;
+ git_index_entry const *result = NULL;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ if (conflict->our_status == GIT_DELTA_RENAMED ||
+ conflict->their_status == GIT_DELTA_RENAMED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+ ours_theirs_differ = ours_changed && theirs_changed &&
+ index_entry_cmp(&conflict->our_entry, &conflict->their_entry);
+
+ /*
+ * Note: with only one ancestor, some cases are not distinct:
+ *
+ * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge
+ * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge
+ * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge
+ *
+ * Note that the two cases that take D/F conflicts into account
+ * specifically do not need to be explicitly tested, as D/F conflicts
+ * would fail the *empty* test:
+ *
+ * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head
+ * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote
+ *
+ * Note that many of these cases need not be explicitly tested, as
+ * they simply degrade to "all different" cases (eg, 11):
+ *
+ * 4: ancest:(empty)^, head:head, remote:remote = result:no merge
+ * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge
+ * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge
+ * 11: ancest:ancest+, head:head, remote:remote = result:no merge
+ */
+
+ /* 5ALT: ancest:*, head:head, remote:head = result:head */
+ if (ours_changed && !ours_empty && !ours_theirs_differ)
+ result = &conflict->our_entry;
+ /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */
+ else if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 0;
+ /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 0;
+ /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 0;
+ /* 13: ancest:ancest+, head:head, remote:ancest = result:head */
+ else if (ours_changed && !theirs_changed)
+ result = &conflict->our_entry;
+ /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */
+ else if (!ours_changed && theirs_changed)
+ result = &conflict->their_entry;
+ else
+ *resolved = 0;
+
+ if (result != NULL &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(*result) &&
+ (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0)
+ *resolved = 1;
+
+ /* Note: trivial resolution does not update the REUC. */
+
+ return error;
+}
+
+static int merge_conflict_resolve_one_removed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+
+ /* Removed in both */
+ if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 1;
+ /* Removed in ours */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 1;
+ /* Removed in theirs */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 1;
+
+ if (*resolved)
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+static int merge_conflict_resolve_one_renamed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_renamed, theirs_renamed;
+ int ours_changed, theirs_changed;
+ git_index_entry *merged;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return 0;
+
+ ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED);
+ theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED);
+
+ if (!ours_renamed && !theirs_renamed)
+ return 0;
+
+ /* Reject one file in a 2->1 conflict */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0);
+ theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0);
+
+ /* if both are modified (and not to a common target) require a merge */
+ if (ours_changed && theirs_changed &&
+ git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0)
+ return 0;
+
+ if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL)
+ return -1;
+
+ if (ours_changed)
+ memcpy(merged, &conflict->our_entry, sizeof(git_index_entry));
+ else
+ memcpy(merged, &conflict->their_entry, sizeof(git_index_entry));
+
+ if (ours_renamed)
+ merged->path = conflict->our_entry.path;
+ else
+ merged->path = conflict->their_entry.path;
+
+ *resolved = 1;
+
+ git_vector_insert(&diff_list->staged, merged);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+static bool merge_conflict_can_resolve_contents(
+ const git_merge_diff *conflict)
+{
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return false;
+
+ /* Reject D/F conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE)
+ return false;
+
+ /* Reject submodules. */
+ if (S_ISGITLINK(conflict->ancestor_entry.mode) ||
+ S_ISGITLINK(conflict->our_entry.mode) ||
+ S_ISGITLINK(conflict->their_entry.mode))
+ return false;
+
+ /* Reject link/file conflicts. */
+ if ((S_ISLNK(conflict->ancestor_entry.mode) ^
+ S_ISLNK(conflict->our_entry.mode)) ||
+ (S_ISLNK(conflict->ancestor_entry.mode) ^
+ S_ISLNK(conflict->their_entry.mode)))
+ return false;
+
+ /* Reject name conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return false;
+
+ if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0)
+ return false;
+
+ return true;
+}
+
+static int merge_conflict_invoke_driver(
+ git_index_entry **out,
+ const char *name,
+ git_merge_driver *driver,
+ git_merge_diff_list *diff_list,
+ git_merge_driver_source *src)
+{
+ git_index_entry *result;
+ git_buf buf = GIT_BUF_INIT;
+ const char *path;
+ uint32_t mode;
+ git_odb *odb = NULL;
+ git_oid oid;
+ int error;
+
+ *out = NULL;
+
+ if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 ||
+ (error = git_repository_odb(&odb, src->repo)) < 0 ||
+ (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)) < 0)
+ goto done;
+
+ result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry));
+ GITERR_CHECK_ALLOC(result);
+
+ git_oid_cpy(&result->id, &oid);
+ result->mode = mode;
+ result->file_size = buf.size;
+
+ result->path = git_pool_strdup(&diff_list->pool, path);
+ GITERR_CHECK_ALLOC(result->path);
+
+ *out = result;
+
+done:
+ git_buf_free(&buf);
+ git_odb_free(odb);
+
+ return error;
+}
+
+static int merge_conflict_resolve_contents(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ const git_merge_options *merge_opts,
+ const git_merge_file_options *file_opts)
+{
+ git_merge_driver_source source = {0};
+ git_merge_file_result result = {0};
+ git_merge_driver *driver;
+ git_merge_driver__builtin builtin = {{0}};
+ git_index_entry *merge_result;
+ git_odb *odb = NULL;
+ const char *name;
+ bool fallback = false;
+ int error;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (!merge_conflict_can_resolve_contents(conflict))
+ return 0;
+
+ source.repo = diff_list->repo;
+ source.default_driver = merge_opts->default_driver;
+ source.file_opts = file_opts;
+ source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+ source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+ source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) {
+ /* if the user requested a particular type of resolution (via the
+ * favor flag) then let that override the gitattributes and use
+ * the builtin driver.
+ */
+ name = "text";
+ builtin.base.apply = git_merge_driver__builtin_apply;
+ builtin.favor = file_opts->favor;
+
+ driver = &builtin.base;
+ } else {
+ /* find the merge driver for this file */
+ if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0)
+ goto done;
+
+ if (driver == NULL)
+ fallback = true;
+ }
+
+ if (driver) {
+ error = merge_conflict_invoke_driver(&merge_result, name, driver,
+ diff_list, &source);
+
+ if (error == GIT_PASSTHROUGH)
+ fallback = true;
+ }
+
+ if (fallback) {
+ error = merge_conflict_invoke_driver(&merge_result, "text",
+ &git_merge_driver__text.base, diff_list, &source);
+ }
+
+ if (error < 0) {
+ if (error == GIT_EMERGECONFLICT)
+ error = 0;
+
+ goto done;
+ }
+
+ git_vector_insert(&diff_list->staged, merge_result);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ *resolved = 1;
+
+done:
+ git_merge_file_result_free(&result);
+ git_odb_free(odb);
+
+ return error;
+}
+
+static int merge_conflict_resolve(
+ int *out,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ const git_merge_options *merge_opts,
+ const git_merge_file_options *file_opts)
+{
+ int resolved = 0;
+ int error = 0;
+
+ *out = 0;
+
+ if ((error = merge_conflict_resolve_trivial(
+ &resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_one_removed(
+ &resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_one_renamed(
+ &resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_contents(
+ &resolved, diff_list, conflict, merge_opts, file_opts)) < 0)
+ goto done;
+
+ *out = resolved;
+
+done:
+ return error;
+}
+
+/* Rename detection and coalescing */
+
+struct merge_diff_similarity {
+ unsigned char similarity;
+ size_t other_idx;
+};
+
+static int index_entry_similarity_exact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_options *opts)
+{
+ GIT_UNUSED(repo);
+ GIT_UNUSED(a_idx);
+ GIT_UNUSED(b_idx);
+ GIT_UNUSED(cache);
+ GIT_UNUSED(opts);
+
+ if (git_oid__cmp(&a->id, &b->id) == 0)
+ return 100;
+
+ return 0;
+}
+
+static int index_entry_similarity_calc(
+ void **out,
+ git_repository *repo,
+ git_index_entry *entry,
+ const git_merge_options *opts)
+{
+ git_blob *blob;
+ git_diff_file diff_file = {{{0}}};
+ git_off_t blobsize;
+ int error;
+
+ *out = NULL;
+
+ if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0)
+ return error;
+
+ git_oid_cpy(&diff_file.id, &entry->id);
+ diff_file.path = entry->path;
+ diff_file.size = entry->file_size;
+ diff_file.mode = entry->mode;
+ diff_file.flags = 0;
+
+ blobsize = git_blob_rawsize(blob);
+
+ /* file too big for rename processing */
+ if (!git__is_sizet(blobsize))
+ return 0;
+
+ error = opts->metric->buffer_signature(out, &diff_file,
+ git_blob_rawcontent(blob), (size_t)blobsize,
+ opts->metric->payload);
+
+ git_blob_free(blob);
+
+ return error;
+}
+
+static int index_entry_similarity_inexact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_options *opts)
+{
+ int score = 0;
+ int error = 0;
+
+ if (GIT_MODE_TYPE(a->mode) != GIT_MODE_TYPE(b->mode))
+ return 0;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0)
+ return error;
+
+ /* some metrics may not wish to process this file (too big / too small) */
+ if (!cache[a_idx] || !cache[b_idx])
+ return 0;
+
+ /* compare signatures */
+ if (opts->metric->similarity(
+ &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)
+ return -1;
+
+ /* clip score */
+ if (score < 0)
+ score = 0;
+ else if (score > 100)
+ score = 100;
+
+ return score;
+}
+
+static int merge_diff_mark_similarity(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ int (*similarity_fn)(git_repository *, git_index_entry *, size_t, git_index_entry *, size_t, void **, const git_merge_options *),
+ void **cache,
+ const git_merge_options *opts)
+{
+ size_t i, j;
+ git_merge_diff *conflict_src, *conflict_tgt;
+ int similarity;
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict_src) {
+ /* Items can be the source of a rename iff they have an item in the
+ * ancestor slot and lack an item in the ours or theirs slot. */
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) ||
+ (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)))
+ continue;
+
+ git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) {
+ size_t our_idx = diff_list->conflicts.length + j;
+ size_t their_idx = (diff_list->conflicts.length * 2) + j;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry))
+ continue;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts);
+
+ if (similarity == GIT_EBUFS)
+ continue;
+ else if (similarity < 0)
+ return similarity;
+
+ if (similarity > similarity_ours[i].similarity &&
+ similarity > similarity_ours[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_ours[i].similarity > 0)
+ similarity_ours[similarity_ours[i].other_idx].similarity = 0;
+
+ if (similarity_ours[j].similarity > 0)
+ similarity_ours[similarity_ours[j].other_idx].similarity = 0;
+
+ similarity_ours[i].similarity = similarity;
+ similarity_ours[i].other_idx = j;
+
+ similarity_ours[j].similarity = similarity;
+ similarity_ours[j].other_idx = i;
+ }
+ }
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts);
+
+ if (similarity > similarity_theirs[i].similarity &&
+ similarity > similarity_theirs[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_theirs[i].similarity > 0)
+ similarity_theirs[similarity_theirs[i].other_idx].similarity = 0;
+
+ if (similarity_theirs[j].similarity > 0)
+ similarity_theirs[similarity_theirs[j].other_idx].similarity = 0;
+
+ similarity_theirs[i].similarity = similarity;
+ similarity_theirs[i].other_idx = j;
+
+ similarity_theirs[j].similarity = similarity;
+ similarity_theirs[j].other_idx = i;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Rename conflicts:
+ *
+ * Ancestor Ours Theirs
+ *
+ * 0a A A A No rename
+ * b A A* A No rename (ours was rewritten)
+ * c A A A* No rename (theirs rewritten)
+ * 1a A A B[A] Rename or rename/edit
+ * b A B[A] A (automergeable)
+ * 2 A B[A] B[A] Both renamed (automergeable)
+ * 3a A B[A] Rename/delete
+ * b A B[A] (same)
+ * 4a A B[A] B Rename/add [B~ours B~theirs]
+ * b A B B[A] (same)
+ * 5 A B[A] C[A] Both renamed ("1 -> 2")
+ * 6 A C[A] Both renamed ("2 -> 1")
+ * B C[B] [C~ours C~theirs] (automergeable)
+ */
+static void merge_diff_mark_rename_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ bool ours_renamed,
+ size_t ours_source_idx,
+ struct merge_diff_similarity *similarity_theirs,
+ bool theirs_renamed,
+ size_t theirs_source_idx,
+ git_merge_diff *target,
+ const git_merge_options *opts)
+{
+ git_merge_diff *ours_source = NULL, *theirs_source = NULL;
+
+ if (ours_renamed)
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ if (theirs_renamed)
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ /* Detect 2->1 conflicts */
+ if (ours_renamed && theirs_renamed) {
+ /* Both renamed to the same target name. */
+ if (ours_source_idx == theirs_source_idx)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED;
+ else {
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ }
+ } else if (ours_renamed) {
+ /* If our source was also renamed in theirs, this is a 1->2 */
+ if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) {
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry))
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ } else if (theirs_renamed) {
+ /* If their source was also renamed in ours, this is a 1->2 */
+ if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold)
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) {
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry))
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ }
+}
+
+GIT_INLINE(void) merge_diff_coalesce_rename(
+ git_index_entry *source_entry,
+ git_delta_t *source_status,
+ git_index_entry *target_entry,
+ git_delta_t *target_status)
+{
+ /* Coalesce the rename target into the rename source. */
+ memcpy(source_entry, target_entry, sizeof(git_index_entry));
+ *source_status = GIT_DELTA_RENAMED;
+
+ memset(target_entry, 0x0, sizeof(git_index_entry));
+ *target_status = GIT_DELTA_UNMODIFIED;
+}
+
+static void merge_diff_list_coalesce_renames(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ const git_merge_options *opts)
+{
+ size_t i;
+ bool ours_renamed = 0, theirs_renamed = 0;
+ size_t ours_source_idx = 0, theirs_source_idx = 0;
+ git_merge_diff *ours_source, *theirs_source, *target;
+
+ for (i = 0; i < diff_list->conflicts.length; i++) {
+ target = diff_list->conflicts.contents[i];
+
+ ours_renamed = 0;
+ theirs_renamed = 0;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) &&
+ similarity_ours[i].similarity >= opts->rename_threshold) {
+ ours_source_idx = similarity_ours[i].other_idx;
+
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ merge_diff_coalesce_rename(
+ &ours_source->our_entry,
+ &ours_source->our_status,
+ &target->our_entry,
+ &target->our_status);
+
+ similarity_ours[ours_source_idx].similarity = 0;
+ similarity_ours[i].similarity = 0;
+
+ ours_renamed = 1;
+ }
+
+ /* insufficient to determine direction */
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) &&
+ similarity_theirs[i].similarity >= opts->rename_threshold) {
+ theirs_source_idx = similarity_theirs[i].other_idx;
+
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ merge_diff_coalesce_rename(
+ &theirs_source->their_entry,
+ &theirs_source->their_status,
+ &target->their_entry,
+ &target->their_status);
+
+ similarity_theirs[theirs_source_idx].similarity = 0;
+ similarity_theirs[i].similarity = 0;
+
+ theirs_renamed = 1;
+ }
+
+ merge_diff_mark_rename_conflict(diff_list,
+ similarity_ours, ours_renamed, ours_source_idx,
+ similarity_theirs, theirs_renamed, theirs_source_idx,
+ target, opts);
+ }
+}
+
+static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p)
+{
+ git_merge_diff *conflict = conflicts->contents[idx];
+
+ GIT_UNUSED(p);
+
+ return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry));
+}
+
+static void merge_diff_list_count_candidates(
+ git_merge_diff_list *diff_list,
+ size_t *src_count,
+ size_t *tgt_count)
+{
+ git_merge_diff *entry;
+ size_t i;
+
+ *src_count = 0;
+ *tgt_count = 0;
+
+ git_vector_foreach(&diff_list->conflicts, i, entry) {
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) &&
+ (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry)))
+ (*src_count)++;
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry))
+ (*tgt_count)++;
+ }
+}
+
+int git_merge_diff_list__find_renames(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ const git_merge_options *opts)
+{
+ struct merge_diff_similarity *similarity_ours, *similarity_theirs;
+ void **cache = NULL;
+ size_t cache_size = 0;
+ size_t src_count, tgt_count, i;
+ int error = 0;
+
+ assert(diff_list && opts);
+
+ if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0)
+ return 0;
+
+ similarity_ours = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_ours);
+
+ similarity_theirs = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_theirs);
+
+ /* Calculate similarity between items that were deleted from the ancestor
+ * and added in the other branch.
+ */
+ if ((error = merge_diff_mark_similarity(repo, diff_list, similarity_ours,
+ similarity_theirs, index_entry_similarity_exact, NULL, opts)) < 0)
+ goto done;
+
+ if (diff_list->conflicts.length <= opts->target_limit) {
+ GITERR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3);
+ cache = git__calloc(cache_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(cache);
+
+ merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count);
+
+ if (src_count > opts->target_limit || tgt_count > opts->target_limit) {
+ /* TODO: report! */
+ } else {
+ if ((error = merge_diff_mark_similarity(
+ repo, diff_list, similarity_ours, similarity_theirs,
+ index_entry_similarity_inexact, cache, opts)) < 0)
+ goto done;
+ }
+ }
+
+ /* For entries that are appropriately similar, merge the new name's entry
+ * into the old name.
+ */
+ merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts);
+
+ /* And remove any entries that were merged and are now empty. */
+ git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL);
+
+done:
+ if (cache != NULL) {
+ for (i = 0; i < cache_size; ++i) {
+ if (cache[i] != NULL)
+ opts->metric->free_signature(cache[i], opts->metric->payload);
+ }
+
+ git__free(cache);
+ }
+
+ git__free(similarity_ours);
+ git__free(similarity_theirs);
+
+ return error;
+}
+
+/* Directory/file conflict handling */
+
+GIT_INLINE(const char *) merge_diff_path(
+ const git_merge_diff *conflict)
+{
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ return conflict->ancestor_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry))
+ return conflict->our_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return conflict->their_entry.path;
+
+ return NULL;
+}
+
+GIT_INLINE(bool) merge_diff_any_side_added_or_modified(
+ const git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED ||
+ conflict->our_status == GIT_DELTA_MODIFIED ||
+ conflict->their_status == GIT_DELTA_ADDED ||
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child)
+{
+ size_t child_len = strlen(child);
+ size_t parent_len = strlen(parent);
+
+ if (child_len < parent_len ||
+ strncmp(parent, child, parent_len) != 0)
+ return 0;
+
+ return (child[parent_len] == '/');
+}
+
+GIT_INLINE(int) merge_diff_detect_df_conflict(
+ struct merge_diff_df_data *df_data,
+ git_merge_diff *conflict)
+{
+ const char *cur_path = merge_diff_path(conflict);
+
+ /* Determine if this is a D/F conflict or the child of one */
+ if (df_data->df_path &&
+ path_is_prefixed(df_data->df_path, cur_path))
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+ else if(df_data->df_path)
+ df_data->df_path = NULL;
+ else if (df_data->prev_path &&
+ merge_diff_any_side_added_or_modified(df_data->prev_conflict) &&
+ merge_diff_any_side_added_or_modified(conflict) &&
+ path_is_prefixed(df_data->prev_path, cur_path)) {
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+
+ df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE;
+ df_data->df_path = df_data->prev_path;
+ }
+
+ df_data->prev_path = cur_path;
+ df_data->prev_conflict = conflict;
+
+ return 0;
+}
+
+/* Conflict handling */
+
+GIT_INLINE(int) merge_diff_detect_type(
+ git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED &&
+ conflict->their_status == GIT_DELTA_ADDED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_ADDED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_DELETED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else
+ conflict->type = GIT_MERGE_DIFF_NONE;
+
+ return 0;
+}
+
+GIT_INLINE(int) index_entry_dup_pool(
+ git_index_entry *out,
+ git_pool *pool,
+ const git_index_entry *src)
+{
+ if (src != NULL) {
+ memcpy(out, src, sizeof(git_index_entry));
+ if ((out->path = git_pool_strdup(pool, src->path)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) merge_delta_type_from_index_entries(
+ const git_index_entry *ancestor,
+ const git_index_entry *other)
+{
+ if (ancestor == NULL && other == NULL)
+ return GIT_DELTA_UNMODIFIED;
+ else if (ancestor == NULL && other != NULL)
+ return GIT_DELTA_ADDED;
+ else if (ancestor != NULL && other == NULL)
+ return GIT_DELTA_DELETED;
+ else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if (git_oid__cmp(&ancestor->id, &other->id) ||
+ ancestor->mode != other->mode)
+ return GIT_DELTA_MODIFIED;
+
+ return GIT_DELTA_UNMODIFIED;
+}
+
+static git_merge_diff *merge_diff_from_index_entries(
+ git_merge_diff_list *diff_list,
+ const git_index_entry **entries)
+{
+ git_merge_diff *conflict;
+ git_pool *pool = &diff_list->pool;
+
+ if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL)
+ return NULL;
+
+ if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 ||
+ index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 ||
+ index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0)
+ return NULL;
+
+ conflict->our_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]);
+ conflict->their_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]);
+
+ return conflict;
+}
+
+/* Merge trees */
+
+static int merge_diff_list_insert_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_df_data *merge_df_data,
+ const git_index_entry *tree_items[3])
+{
+ git_merge_diff *conflict;
+
+ if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL ||
+ merge_diff_detect_type(conflict) < 0 ||
+ merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 ||
+ git_vector_insert(&diff_list->conflicts, conflict) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int merge_diff_list_insert_unmodified(
+ git_merge_diff_list *diff_list,
+ const git_index_entry *tree_items[3])
+{
+ int error = 0;
+ git_index_entry *entry;
+
+ entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0)
+ error = git_vector_insert(&diff_list->staged, entry);
+
+ return error;
+}
+
+struct merge_diff_find_data {
+ git_merge_diff_list *diff_list;
+ struct merge_diff_df_data df_data;
+};
+
+static int queue_difference(const git_index_entry **entries, void *data)
+{
+ struct merge_diff_find_data *find_data = data;
+ bool item_modified = false;
+ size_t i;
+
+ if (!entries[0] || !entries[1] || !entries[2]) {
+ item_modified = true;
+ } else {
+ for (i = 1; i < 3; i++) {
+ if (index_entry_cmp(entries[0], entries[i]) != 0) {
+ item_modified = true;
+ break;
+ }
+ }
+ }
+
+ return item_modified ?
+ merge_diff_list_insert_conflict(
+ find_data->diff_list, &find_data->df_data, entries) :
+ merge_diff_list_insert_unmodified(find_data->diff_list, entries);
+}
+
+int git_merge_diff_list__find_differences(
+ git_merge_diff_list *diff_list,
+ git_iterator *ancestor_iter,
+ git_iterator *our_iter,
+ git_iterator *their_iter)
+{
+ git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
+ struct merge_diff_find_data find_data = { diff_list };
+
+ return git_iterator_walk(iterators, 3, queue_difference, &find_data);
+}
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
+{
+ git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list));
+
+ if (diff_list == NULL)
+ return NULL;
+
+ diff_list->repo = repo;
+
+ git_pool_init(&diff_list->pool, 1);
+
+ if (git_vector_init(&diff_list->staged, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->conflicts, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->resolved, 0, NULL) < 0) {
+ git_merge_diff_list__free(diff_list);
+ return NULL;
+ }
+
+ return diff_list;
+}
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list)
+{
+ if (!diff_list)
+ return;
+
+ git_vector_free(&diff_list->staged);
+ git_vector_free(&diff_list->conflicts);
+ git_vector_free(&diff_list->resolved);
+ git_pool_clear(&diff_list->pool);
+ git__free(diff_list);
+}
+
+static int merge_normalize_opts(
+ git_repository *repo,
+ git_merge_options *opts,
+ const git_merge_options *given)
+{
+ git_config *cfg = NULL;
+ git_config_entry *entry = NULL;
+ int error = 0;
+
+ assert(repo && opts);
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_merge_options));
+ else {
+ git_merge_options init = GIT_MERGE_OPTIONS_INIT;
+ memcpy(opts, &init, sizeof(init));
+
+ opts->flags = GIT_MERGE_FIND_RENAMES;
+ opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD;
+ }
+
+ if (given && given->default_driver) {
+ opts->default_driver = git__strdup(given->default_driver);
+ GITERR_CHECK_ALLOC(opts->default_driver);
+ } else {
+ error = git_config_get_entry(&entry, cfg, "merge.default");
+
+ if (error == 0) {
+ opts->default_driver = git__strdup(entry->value);
+ GITERR_CHECK_ALLOC(opts->default_driver);
+ } else if (error == GIT_ENOTFOUND) {
+ error = 0;
+ } else {
+ goto done;
+ }
+ }
+
+ if (!opts->target_limit) {
+ int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0);
+
+ if (!limit)
+ limit = git_config__get_int_force(cfg, "diff.renamelimit", 0);
+
+ opts->target_limit = (limit <= 0) ?
+ GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit;
+ }
+
+ /* assign the internal metric with whitespace flag as payload */
+ if (!opts->metric) {
+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
+ GITERR_CHECK_ALLOC(opts->metric);
+
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
+ opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;
+ }
+
+done:
+ git_config_entry_free(entry);
+ return error;
+}
+
+
+static int merge_index_insert_reuc(
+ git_index *index,
+ size_t idx,
+ const git_index_entry *entry)
+{
+ const git_index_reuc_entry *reuc;
+ int mode[3] = { 0, 0, 0 };
+ git_oid const *oid[3] = { NULL, NULL, NULL };
+ size_t i;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry))
+ return 0;
+
+ if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) {
+ for (i = 0; i < 3; i++) {
+ mode[i] = reuc->mode[i];
+ oid[i] = &reuc->oid[i];
+ }
+ }
+
+ mode[idx] = entry->mode;
+ oid[idx] = &entry->id;
+
+ return git_index_reuc_add(index, entry->path,
+ mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]);
+}
+
+static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list)
+{
+ int error;
+ size_t i;
+ git_merge_diff *conflict;
+
+ /* Add each entry in the resolved conflict to the REUC independently, since
+ * the paths may differ due to renames. */
+ git_vector_foreach(&diff_list->resolved, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if (ancestor != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0)
+ return error;
+
+ if (ours != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0)
+ return error;
+
+ if (theirs != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int index_from_diff_list(git_index **out,
+ git_merge_diff_list *diff_list, bool skip_reuc)
+{
+ git_index *index;
+ size_t i;
+ git_merge_diff *conflict;
+ int error = 0;
+
+ *out = NULL;
+
+ if ((error = git_index_new(&index)) < 0)
+ return error;
+
+ if ((error = git_index__fill(index, &diff_list->staged)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0)
+ goto on_error;
+ }
+
+ /* Add each rename entry to the rename portion of the index. */
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const char *ancestor_path, *our_path, *their_path;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ continue;
+
+ ancestor_path = conflict->ancestor_entry.path;
+
+ our_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ conflict->our_entry.path : NULL;
+
+ their_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ conflict->their_entry.path : NULL;
+
+ if ((our_path && strcmp(ancestor_path, our_path) != 0) ||
+ (their_path && strcmp(ancestor_path, their_path) != 0)) {
+ if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0)
+ goto on_error;
+ }
+ }
+
+ if (!skip_reuc) {
+ if ((error = index_update_reuc(index, diff_list)) < 0)
+ goto on_error;
+ }
+
+ *out = index;
+ return 0;
+
+on_error:
+ git_index_free(index);
+ return error;
+}
+
+static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given)
+{
+ git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ if (given)
+ return given;
+
+ opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if (git_iterator_for_nothing(empty, &opts) < 0)
+ return NULL;
+
+ return *empty;
+}
+
+int git_merge__iterators(
+ git_index **out,
+ git_repository *repo,
+ git_iterator *ancestor_iter,
+ git_iterator *our_iter,
+ git_iterator *theirs_iter,
+ const git_merge_options *given_opts)
+{
+ git_iterator *empty_ancestor = NULL,
+ *empty_ours = NULL,
+ *empty_theirs = NULL;
+ git_merge_diff_list *diff_list;
+ git_merge_options opts;
+ git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ git_merge_diff *conflict;
+ git_vector changes;
+ size_t i;
+ int error = 0;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ GITERR_CHECK_VERSION(
+ given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
+
+ if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0)
+ return error;
+
+ file_opts.favor = opts.file_favor;
+ file_opts.flags = opts.file_flags;
+
+ /* use the git-inspired labels when virtual base building */
+ if (opts.flags & GIT_MERGE__VIRTUAL_BASE) {
+ file_opts.ancestor_label = "merged common ancestors";
+ file_opts.our_label = "Temporary merge branch 1";
+ file_opts.their_label = "Temporary merge branch 2";
+ file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED;
+ }
+
+ diff_list = git_merge_diff_list__alloc(repo);
+ GITERR_CHECK_ALLOC(diff_list);
+
+ ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter);
+ our_iter = iterator_given_or_empty(&empty_ours, our_iter);
+ theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter);
+
+ if ((error = git_merge_diff_list__find_differences(
+ diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 ||
+ (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0)
+ goto done;
+
+ memcpy(&changes, &diff_list->conflicts, sizeof(git_vector));
+ git_vector_clear(&diff_list->conflicts);
+
+ git_vector_foreach(&changes, i, conflict) {
+ int resolved = 0;
+
+ if ((error = merge_conflict_resolve(
+ &resolved, diff_list, conflict, &opts, &file_opts)) < 0)
+ goto done;
+
+ if (!resolved) {
+ if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) {
+ giterr_set(GITERR_MERGE, "merge conflicts exist");
+ error = GIT_EMERGECONFLICT;
+ goto done;
+ }
+
+ git_vector_insert(&diff_list->conflicts, conflict);
+ }
+ }
+
+ error = index_from_diff_list(out, diff_list,
+ (opts.flags & GIT_MERGE_SKIP_REUC));
+
+done:
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ git__free((char *)opts.default_driver);
+
+ git_merge_diff_list__free(diff_list);
+ git_iterator_free(empty_ancestor);
+ git_iterator_free(empty_ours);
+ git_iterator_free(empty_theirs);
+
+ return error;
+}
+
+int git_merge_trees(
+ git_index **out,
+ git_repository *repo,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree,
+ const git_merge_options *merge_opts)
+{
+ git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error;
+
+ iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_iterator_for_tree(
+ &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 ||
+ (error = git_iterator_for_tree(
+ &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 ||
+ (error = git_iterator_for_tree(
+ &their_iter, (git_tree *)their_tree, &iter_opts)) < 0)
+ goto done;
+
+ error = git_merge__iterators(
+ out, repo, ancestor_iter, our_iter, their_iter, merge_opts);
+
+done:
+ git_iterator_free(ancestor_iter);
+ git_iterator_free(our_iter);
+ git_iterator_free(their_iter);
+
+ return error;
+}
+
+static int merge_annotated_commits(
+ git_index **index_out,
+ git_annotated_commit **base_out,
+ git_repository *repo,
+ git_annotated_commit *our_commit,
+ git_annotated_commit *their_commit,
+ size_t recursion_level,
+ const git_merge_options *opts);
+
+GIT_INLINE(int) insert_head_ids(
+ git_array_oid_t *ids,
+ const git_annotated_commit *annotated_commit)
+{
+ git_oid *id;
+ size_t i;
+
+ if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) {
+ id = git_array_alloc(*ids);
+ GITERR_CHECK_ALLOC(id);
+
+ git_oid_cpy(id, git_commit_id(annotated_commit->commit));
+ } else {
+ for (i = 0; i < annotated_commit->parents.size; i++) {
+ id = git_array_alloc(*ids);
+ GITERR_CHECK_ALLOC(id);
+
+ git_oid_cpy(id, &annotated_commit->parents.ptr[i]);
+ }
+ }
+
+ return 0;
+}
+
+static int create_virtual_base(
+ git_annotated_commit **out,
+ git_repository *repo,
+ git_annotated_commit *one,
+ git_annotated_commit *two,
+ const git_merge_options *opts,
+ size_t recursion_level)
+{
+ git_annotated_commit *result = NULL;
+ git_index *index = NULL;
+ git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT;
+
+ /* Conflicts in the merge base creation do not propagate to conflicts
+ * in the result; the conflicted base will act as the common ancestor.
+ */
+ if (opts)
+ memcpy(&virtual_opts, opts, sizeof(git_merge_options));
+
+ virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT;
+ virtual_opts.flags |= GIT_MERGE__VIRTUAL_BASE;
+
+ if ((merge_annotated_commits(&index, NULL, repo, one, two,
+ recursion_level + 1, &virtual_opts)) < 0)
+ return -1;
+
+ result = git__calloc(1, sizeof(git_annotated_commit));
+ GITERR_CHECK_ALLOC(result);
+ result->type = GIT_ANNOTATED_COMMIT_VIRTUAL;
+ result->index = index;
+
+ insert_head_ids(&result->parents, one);
+ insert_head_ids(&result->parents, two);
+
+ *out = result;
+ return 0;
+}
+
+static int compute_base(
+ git_annotated_commit **out,
+ git_repository *repo,
+ const git_annotated_commit *one,
+ const git_annotated_commit *two,
+ const git_merge_options *given_opts,
+ size_t recursion_level)
+{
+ git_array_oid_t head_ids = GIT_ARRAY_INIT;
+ git_oidarray bases = {0};
+ git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+ size_t i;
+ int error;
+
+ *out = NULL;
+
+ if (given_opts)
+ memcpy(&opts, given_opts, sizeof(git_merge_options));
+
+ if ((error = insert_head_ids(&head_ids, one)) < 0 ||
+ (error = insert_head_ids(&head_ids, two)) < 0)
+ goto done;
+
+ if ((error = git_merge_bases_many(&bases, repo,
+ head_ids.size, head_ids.ptr)) < 0 ||
+ (error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0 ||
+ (opts.flags & GIT_MERGE_NO_RECURSIVE))
+ goto done;
+
+ for (i = 1; i < bases.count; i++) {
+ recursion_level++;
+
+ if (opts.recursion_limit && recursion_level > opts.recursion_limit)
+ break;
+
+ if ((error = git_annotated_commit_lookup(&other, repo,
+ &bases.ids[i])) < 0 ||
+ (error = create_virtual_base(&new_base, repo, base, other, &opts,
+ recursion_level)) < 0)
+ goto done;
+
+ git_annotated_commit_free(base);
+ git_annotated_commit_free(other);
+
+ base = new_base;
+ new_base = NULL;
+ other = NULL;
+ }
+
+done:
+ if (error == 0)
+ *out = base;
+ else
+ git_annotated_commit_free(base);
+
+ git_annotated_commit_free(other);
+ git_annotated_commit_free(new_base);
+ git_oidarray_free(&bases);
+ git_array_clear(head_ids);
+ return error;
+}
+
+static int iterator_for_annotated_commit(
+ git_iterator **out,
+ git_annotated_commit *commit)
+{
+ git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error;
+
+ opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if (commit == NULL) {
+ error = git_iterator_for_nothing(out, &opts);
+ } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) {
+ error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts);
+ } else {
+ if (!commit->tree &&
+ (error = git_commit_tree(&commit->tree, commit->commit)) < 0)
+ goto done;
+
+ error = git_iterator_for_tree(out, commit->tree, &opts);
+ }
+
+done:
+ return error;
+}
+
+static int merge_annotated_commits(
+ git_index **index_out,
+ git_annotated_commit **base_out,
+ git_repository *repo,
+ git_annotated_commit *ours,
+ git_annotated_commit *theirs,
+ size_t recursion_level,
+ const git_merge_options *opts)
+{
+ git_annotated_commit *base = NULL;
+ git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL;
+ int error;
+
+ if ((error = compute_base(&base, repo, ours, theirs, opts,
+ recursion_level)) < 0) {
+
+ if (error != GIT_ENOTFOUND)
+ goto done;
+
+ giterr_clear();
+ }
+
+ if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 ||
+ (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 ||
+ (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 ||
+ (error = git_merge__iterators(index_out, repo, base_iter, our_iter,
+ their_iter, opts)) < 0)
+ goto done;
+
+ if (base_out) {
+ *base_out = base;
+ base = NULL;
+ }
+
+done:
+ git_annotated_commit_free(base);
+ git_iterator_free(base_iter);
+ git_iterator_free(our_iter);
+ git_iterator_free(their_iter);
+ return error;
+}
+
+
+int git_merge_commits(
+ git_index **out,
+ git_repository *repo,
+ const git_commit *our_commit,
+ const git_commit *their_commit,
+ const git_merge_options *opts)
+{
+ git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL;
+ int error = 0;
+
+ if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 ||
+ (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0)
+ goto done;
+
+ error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts);
+
+done:
+ git_annotated_commit_free(ours);
+ git_annotated_commit_free(theirs);
+ git_annotated_commit_free(base);
+ return error;
+}
+
+/* Merge setup / cleanup */
+
+static int write_merge_head(
+ git_repository *repo,
+ const git_annotated_commit *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ size_t i;
+ int error = 0;
+
+ assert(repo && heads);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_mode(git_repository *repo)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ assert(repo);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+struct merge_msg_entry {
+ const git_annotated_commit *merge_head;
+ bool written;
+};
+
+static int msg_entry_is_branch(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0);
+}
+
+static int msg_entry_is_tracking(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0);
+}
+
+static int msg_entry_is_tag(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0);
+}
+
+static int msg_entry_is_remote(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ if (entry->written == 0 &&
+ entry->merge_head->remote_url != NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0)
+ {
+ struct merge_msg_entry *existing;
+
+ /* Match only branches from the same remote */
+ if (entries->length == 0)
+ return 1;
+
+ existing = git_vector_get(entries, 0);
+
+ return (git__strcmp(existing->merge_head->remote_url,
+ entry->merge_head->remote_url) == 0);
+ }
+
+ return 0;
+}
+
+static int msg_entry_is_oid(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 0 &&
+ merge_msg_entry->merge_head->ref_name == NULL &&
+ merge_msg_entry->merge_head->remote_url == NULL);
+}
+
+static int merge_msg_entry_written(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 1);
+}
+
+static int merge_msg_entries(
+ git_vector *v,
+ const struct merge_msg_entry *entries,
+ size_t len,
+ int (*match)(const struct merge_msg_entry *entry, git_vector *entries))
+{
+ size_t i;
+ int matches, total = 0;
+
+ git_vector_clear(v);
+
+ for (i = 0; i < len; i++) {
+ if ((matches = match(&entries[i], v)) < 0)
+ return matches;
+ else if (!matches)
+ continue;
+
+ git_vector_insert(v, (struct merge_msg_entry *)&entries[i]);
+ total++;
+ }
+
+ return total;
+}
+
+static int merge_msg_write_entries(
+ git_filebuf *file,
+ git_vector *entries,
+ const char *item_name,
+ const char *item_plural_name,
+ size_t ref_name_skip,
+ const char *source,
+ char sep)
+{
+ struct merge_msg_entry *entry;
+ size_t i;
+ int error = 0;
+
+ if (entries->length == 0)
+ return 0;
+
+ if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "%s ",
+ (entries->length == 1) ? item_name : item_plural_name)) < 0)
+ goto done;
+
+ git_vector_foreach(entries, i, entry) {
+ if (i > 0 &&
+ (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0)
+ goto done;
+
+ entry->written = 1;
+ }
+
+ if (source)
+ error = git_filebuf_printf(file, " of %s", source);
+
+done:
+ return error;
+}
+
+static int merge_msg_write_branches(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_tracking(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "remote-tracking branch", "remote-tracking branches", 0, NULL, sep);
+}
+
+static int merge_msg_write_tags(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_remotes(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ const char *source;
+
+ if (entries->length == 0)
+ return 0;
+
+ source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url;
+
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep);
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const git_annotated_commit *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ struct merge_msg_entry *entries;
+ git_vector matching = GIT_VECTOR_INIT;
+ size_t i;
+ char sep = 0;
+ int error = 0;
+
+ assert(repo && heads);
+
+ entries = git__calloc(heads_len, sizeof(struct merge_msg_entry));
+ GITERR_CHECK_ALLOC(entries);
+
+ if (git_vector_init(&matching, heads_len, NULL) < 0) {
+ git__free(entries);
+ return -1;
+ }
+
+ for (i = 0; i < heads_len; i++)
+ entries[i].merge_head = heads[i];
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 ||
+ (error = git_filebuf_write(&file, "Merge ", 6)) < 0)
+ goto cleanup;
+
+ /*
+ * This is to emulate the format of MERGE_MSG by core git.
+ *
+ * Core git will write all the commits specified by OID, in the order
+ * provided, until the first named branch or tag is reached, at which
+ * point all branches will be written in the order provided, then all
+ * tags, then all remote tracking branches and finally all commits that
+ * were specified by OID that were not already written.
+ *
+ * Yes. Really.
+ */
+ for (i = 0; i < heads_len; i++) {
+ if (!msg_entry_is_oid(&entries[i]))
+ break;
+
+ if ((error = git_filebuf_printf(&file,
+ "%scommit '%s'", (i > 0) ? "; " : "",
+ entries[i].merge_head->id_str)) < 0)
+ goto cleanup;
+
+ entries[i].written = 1;
+ }
+
+ if (i)
+ sep = ';';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 ||
+ (error = merge_msg_write_branches(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 ||
+ (error = merge_msg_write_tracking(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 ||
+ (error = merge_msg_write_tags(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ /* We should never be called with multiple remote branches, but handle
+ * it in case we are... */
+ while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) {
+ if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if (merge_msg_entry_written(&entries[i]))
+ continue;
+
+ if ((error = git_filebuf_printf(&file, "; commit '%s'",
+ entries[i].merge_head->id_str)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_filebuf_printf(&file, "\n")) < 0 ||
+ (error = git_filebuf_commit(&file)) < 0)
+ goto cleanup;
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ git_vector_free(&matching);
+ git__free(entries);
+
+ return error;
+}
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_annotated_commit *our_head,
+ const git_annotated_commit *heads[],
+ size_t heads_len)
+{
+ int error = 0;
+
+ assert (repo && our_head && heads);
+
+ if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 &&
+ (error = write_merge_head(repo, heads, heads_len)) == 0 &&
+ (error = write_merge_mode(repo)) == 0) {
+ error = write_merge_msg(repo, heads, heads_len);
+ }
+
+ return error;
+}
+
+/* Merge branches */
+
+static int merge_ancestor_head(
+ git_annotated_commit **ancestor_head,
+ git_repository *repo,
+ const git_annotated_commit *our_head,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len)
+{
+ git_oid *oids, ancestor_oid;
+ size_t i, alloc_len;
+ int error = 0;
+
+ assert(repo && our_head && their_heads);
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1);
+ oids = git__calloc(alloc_len, sizeof(git_oid));
+ GITERR_CHECK_ALLOC(oids);
+
+ git_oid_cpy(&oids[0], git_commit_id(our_head->commit));
+
+ for (i = 0; i < their_heads_len; i++)
+ git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i]));
+
+ if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0)
+ goto on_error;
+
+ error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid);
+
+on_error:
+ git__free(oids);
+ return error;
+}
+
+const char *merge_their_label(const char *branchname)
+{
+ const char *slash;
+
+ if ((slash = strrchr(branchname, '/')) == NULL)
+ return branchname;
+
+ if (*(slash+1) == '\0')
+ return "theirs";
+
+ return slash+1;
+}
+
+static int merge_normalize_checkout_opts(
+ git_checkout_options *out,
+ git_repository *repo,
+ const git_checkout_options *given_checkout_opts,
+ unsigned int checkout_strategy,
+ git_annotated_commit *ancestor,
+ const git_annotated_commit *our_head,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len)
+{
+ git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ int error = 0;
+
+ GIT_UNUSED(repo);
+
+ if (given_checkout_opts != NULL)
+ memcpy(out, given_checkout_opts, sizeof(git_checkout_options));
+ else
+ memcpy(out, &default_checkout_opts, sizeof(git_checkout_options));
+
+ out->checkout_strategy = checkout_strategy;
+
+ if (!out->ancestor_label) {
+ if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL)
+ out->ancestor_label = git_commit_summary(ancestor->commit);
+ else if (ancestor)
+ out->ancestor_label = "merged common ancestors";
+ else
+ out->ancestor_label = "empty base";
+ }
+
+ if (!out->our_label) {
+ if (our_head && our_head->ref_name)
+ out->our_label = our_head->ref_name;
+ else
+ out->our_label = "ours";
+ }
+
+ if (!out->their_label) {
+ if (their_heads_len == 1 && their_heads[0]->ref_name)
+ out->their_label = merge_their_label(their_heads[0]->ref_name);
+ else if (their_heads_len == 1)
+ out->their_label = their_heads[0]->id_str;
+ else
+ out->their_label = "theirs";
+ }
+
+ return error;
+}
+
+static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
+{
+ git_tree *head_tree = NULL;
+ git_index *index_repo = NULL;
+ git_iterator *iter_repo = NULL, *iter_new = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_diff *staged_diff_list = NULL, *index_diff_list = NULL;
+ git_diff_delta *delta;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_vector staged_paths = GIT_VECTOR_INIT;
+ size_t i;
+ int error = 0;
+
+ GIT_UNUSED(merged_paths);
+
+ *conflicts = 0;
+
+ /* No staged changes may exist unless the change staged is identical to
+ * the result of the merge. This allows one to apply to merge manually,
+ * then run merge. Any other staged change would be overwritten by
+ * a reset merge.
+ */
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_repository_index(&index_repo, repo)) < 0 ||
+ (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0)
+ goto done;
+
+ if (staged_diff_list->deltas.length == 0)
+ goto done;
+
+ git_vector_foreach(&staged_diff_list->deltas, i, delta) {
+ if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0)
+ goto done;
+ }
+
+ iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ iter_opts.pathlist.strings = (char **)staged_paths.contents;
+ iter_opts.pathlist.count = staged_paths.length;
+
+ if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 ||
+ (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 ||
+ (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0)
+ goto done;
+
+ *conflicts = index_diff_list->deltas.length;
+
+done:
+ git_tree_free(head_tree);
+ git_index_free(index_repo);
+ git_iterator_free(iter_repo);
+ git_iterator_free(iter_new);
+ git_diff_free(staged_diff_list);
+ git_diff_free(index_diff_list);
+ git_vector_free(&staged_paths);
+
+ return error;
+}
+
+static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
+{
+ git_diff *wd_diff_list = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ int error = 0;
+
+ GIT_UNUSED(index_new);
+
+ *conflicts = 0;
+
+ /* We need to have merged at least 1 file for the possibility to exist to
+ * have conflicts with the workdir. Passing 0 as the pathspec count paramter
+ * will consider all files in the working directory, that is, we may detect
+ * a conflict if there were untracked files in the workdir prior to starting
+ * the merge. This typically happens when cherry-picking a commmit whose
+ * changes have already been applied.
+ */
+ if (merged_paths->length == 0)
+ return 0;
+
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /* Workdir changes may exist iff they do not conflict with changes that
+ * will be applied by the merge (including conflicts). Ensure that there
+ * are no changes in the workdir to these paths.
+ */
+ opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ opts.pathspec.count = merged_paths->length;
+ opts.pathspec.strings = (char **)merged_paths->contents;
+ opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL;
+
+ if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0)
+ goto done;
+
+ *conflicts = wd_diff_list->deltas.length;
+
+done:
+ git_diff_free(wd_diff_list);
+
+ return error;
+}
+
+int git_merge__check_result(git_repository *repo, git_index *index_new)
+{
+ git_tree *head_tree = NULL;
+ git_iterator *iter_head = NULL, *iter_new = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_diff *merged_list = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_delta *delta;
+ git_vector paths = GIT_VECTOR_INIT;
+ size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts;
+ const git_index_entry *e;
+ int error = 0;
+
+ iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 ||
+ (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 ||
+ (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0)
+ goto done;
+
+ git_vector_foreach(&merged_list->deltas, i, delta) {
+ if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0)
+ goto done;
+ }
+
+ for (i = 0; i < git_index_entrycount(index_new); i++) {
+ e = git_index_get_byindex(index_new, i);
+
+ if (git_index_entry_is_conflict(e) &&
+ (git_vector_last(&paths) == NULL ||
+ strcmp(git_vector_last(&paths), e->path) != 0)) {
+
+ if ((error = git_vector_insert(&paths, (char *)e->path)) < 0)
+ goto done;
+ }
+ }
+
+ /* Make sure the index and workdir state do not prevent merging */
+ if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 ||
+ (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0)
+ goto done;
+
+ if ((conflicts = index_conflicts + wd_conflicts) > 0) {
+ giterr_set(GITERR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge",
+ conflicts, (conflicts != 1) ? "s" : "");
+ error = GIT_ECONFLICT;
+ }
+
+done:
+ git_vector_free(&paths);
+ git_tree_free(head_tree);
+ git_iterator_free(iter_head);
+ git_iterator_free(iter_new);
+ git_diff_free(merged_list);
+
+ return error;
+}
+
+int git_merge__append_conflicts_to_merge_msg(
+ git_repository *repo,
+ git_index *index)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ const char *last = NULL;
+ size_t i;
+ int error;
+
+ if (!git_index_has_conflicts(index))
+ return 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ git_filebuf_printf(&file, "\nConflicts:\n");
+
+ for (i = 0; i < git_index_entrycount(index); i++) {
+ const git_index_entry *e = git_index_get_byindex(index, i);
+
+ if (!git_index_entry_is_conflict(e))
+ continue;
+
+ if (last == NULL || strcmp(e->path, last) != 0)
+ git_filebuf_printf(&file, "\t%s\n", e->path);
+
+ last = e->path;
+ }
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int merge_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = {
+ GIT_MERGE_HEAD_FILE,
+ GIT_MERGE_MODE_FILE,
+ GIT_MERGE_MSG_FILE,
+ };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int merge_heads(
+ git_annotated_commit **ancestor_head_out,
+ git_annotated_commit **our_head_out,
+ git_repository *repo,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len)
+{
+ git_annotated_commit *ancestor_head = NULL, *our_head = NULL;
+ git_reference *our_ref = NULL;
+ int error = 0;
+
+ *ancestor_head_out = NULL;
+ *our_head_out = NULL;
+
+ if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
+ goto done;
+
+ if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 ||
+ (error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0)
+ goto done;
+
+ if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+
+ giterr_clear();
+ error = 0;
+ }
+
+ *ancestor_head_out = ancestor_head;
+ *our_head_out = our_head;
+
+done:
+ if (error < 0) {
+ git_annotated_commit_free(ancestor_head);
+ git_annotated_commit_free(our_head);
+ }
+
+ git_reference_free(our_ref);
+
+ return error;
+}
+
+static int merge_preference(git_merge_preference_t *out, git_repository *repo)
+{
+ git_config *config;
+ const char *value;
+ int bool_value, error = 0;
+
+ *out = GIT_MERGE_PREFERENCE_NONE;
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ goto done;
+
+ if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ goto done;
+ }
+
+ if (git_config_parse_bool(&bool_value, value) == 0) {
+ if (!bool_value)
+ *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD;
+ } else {
+ if (strcasecmp(value, "only") == 0)
+ *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY;
+ }
+
+done:
+ git_config_free(config);
+ return error;
+}
+
+int git_merge_analysis(
+ git_merge_analysis_t *analysis_out,
+ git_merge_preference_t *preference_out,
+ git_repository *repo,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len)
+{
+ git_annotated_commit *ancestor_head = NULL, *our_head = NULL;
+ int error = 0;
+
+ assert(analysis_out && preference_out && repo && their_heads);
+
+ if (their_heads_len != 1) {
+ giterr_set(GITERR_MERGE, "Can only merge a single branch");
+ error = -1;
+ goto done;
+ }
+
+ *analysis_out = GIT_MERGE_ANALYSIS_NONE;
+
+ if ((error = merge_preference(preference_out, repo)) < 0)
+ goto done;
+
+ if (git_repository_head_unborn(repo)) {
+ *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN;
+ goto done;
+ }
+
+ if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0)
+ goto done;
+
+ /* We're up-to-date if we're trying to merge our own common ancestor. */
+ if (ancestor_head && git_oid_equal(
+ git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0])))
+ *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE;
+
+ /* We're fastforwardable if we're our own common ancestor. */
+ else if (ancestor_head && git_oid_equal(
+ git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head)))
+ *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL;
+
+ /* Otherwise, just a normal merge is possible. */
+ else
+ *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL;
+
+done:
+ git_annotated_commit_free(ancestor_head);
+ git_annotated_commit_free(our_head);
+ return error;
+}
+
+int git_merge(
+ git_repository *repo,
+ const git_annotated_commit **their_heads,
+ size_t their_heads_len,
+ const git_merge_options *merge_opts,
+ const git_checkout_options *given_checkout_opts)
+{
+ git_reference *our_ref = NULL;
+ git_checkout_options checkout_opts;
+ git_annotated_commit *our_head = NULL, *base = NULL;
+ git_index *index = NULL;
+ git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
+ unsigned int checkout_strategy;
+ int error = 0;
+
+ assert(repo && their_heads);
+
+ if (their_heads_len != 1) {
+ giterr_set(GITERR_MERGE, "Can only merge a single branch");
+ return -1;
+ }
+
+ if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
+ goto done;
+
+ checkout_strategy = given_checkout_opts ?
+ given_checkout_opts->checkout_strategy :
+ GIT_CHECKOUT_SAFE;
+
+ if ((error = git_indexwriter_init_for_operation(&indexwriter, repo,
+ &checkout_strategy)) < 0)
+ goto done;
+
+ /* Write the merge setup files to the repository. */
+ if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 ||
+ (error = git_merge__setup(repo, our_head, their_heads,
+ their_heads_len)) < 0)
+ goto done;
+
+ /* TODO: octopus */
+
+ if ((error = merge_annotated_commits(&index, &base, repo, our_head,
+ (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 ||
+ (error = git_merge__check_result(repo, index)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0)
+ goto done;
+
+ /* check out the merge results */
+
+ if ((error = merge_normalize_checkout_opts(&checkout_opts, repo,
+ given_checkout_opts, checkout_strategy,
+ base, our_head, their_heads, their_heads_len)) < 0 ||
+ (error = git_checkout_index(repo, index, &checkout_opts)) < 0)
+ goto done;
+
+ error = git_indexwriter_commit(&indexwriter);
+
+done:
+ if (error < 0)
+ merge_state_cleanup(repo);
+
+ git_indexwriter_cleanup(&indexwriter);
+ git_index_free(index);
+ git_annotated_commit_free(our_head);
+ git_annotated_commit_free(base);
+ git_reference_free(our_ref);
+
+ return error;
+}
+
+int git_merge_init_options(git_merge_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT);
+ return 0;
+}
+
+int git_merge_file_init_input(git_merge_file_input *input, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT);
+ return 0;
+}
+
+int git_merge_file_init_options(
+ git_merge_file_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_merge_h__
+#define INCLUDE_merge_h__
+
+#include "vector.h"
+#include "commit_list.h"
+#include "pool.h"
+#include "iterator.h"
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "git2/sys/merge.h"
+
+#define GIT_MERGE_MSG_FILE "MERGE_MSG"
+#define GIT_MERGE_MODE_FILE "MERGE_MODE"
+#define GIT_MERGE_FILE_MODE 0666
+
+#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50
+#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000
+
+
+/** Internal merge flags. */
+enum {
+ /** The merge is for a virtual base in a recursive merge. */
+ GIT_MERGE__VIRTUAL_BASE = (1 << 31),
+};
+
+enum {
+ /** Accept the conflict file, staging it as the merge result. */
+ GIT_MERGE_FILE_FAVOR__CONFLICTED = 4,
+};
+
+
+/** Types of changes when files are merged from branch to branch. */
+typedef enum {
+ /* No conflict - a change only occurs in one branch. */
+ GIT_MERGE_DIFF_NONE = 0,
+
+ /* Occurs when a file is modified in both branches. */
+ GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0),
+
+ /* Occurs when a file is added in both branches. */
+ GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1),
+
+ /* Occurs when a file is deleted in both branches. */
+ GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2),
+
+ /* Occurs when a file is modified in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3),
+
+ /* Occurs when a file is renamed in one branch and modified in the other. */
+ GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4),
+
+ /* Occurs when a file is renamed in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5),
+
+ /* Occurs when a file is renamed in one branch and a file with the same
+ * name is added in the other. Eg, A->B and new file B. Core git calls
+ * this a "rename/delete". */
+ GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6),
+
+ /* Occurs when both a file is renamed to the same name in the ours and
+ * theirs branches. Eg, A->B and A->B in both. Automergeable. */
+ GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7),
+
+ /* Occurs when a file is renamed to different names in the ours and theirs
+ * branches. Eg, A->B and A->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8),
+
+ /* Occurs when two files are renamed to the same name in the ours and
+ * theirs branches. Eg, A->C and B->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9),
+
+ /* Occurs when an item at a path in one branch is a directory, and an
+ * item at the same path in a different branch is a file. */
+ GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10),
+
+ /* The child of a folder that is in a directory/file conflict. */
+ GIT_MERGE_DIFF_DF_CHILD = (1 << 11),
+} git_merge_diff_type_t;
+
+typedef struct {
+ git_repository *repo;
+ git_pool pool;
+
+ /* Vector of git_index_entry that represent the merged items that
+ * have been staged, either because only one side changed, or because
+ * the two changes were non-conflicting and mergeable. These items
+ * will be written as staged entries in the main index.
+ */
+ git_vector staged;
+
+ /* Vector of git_merge_diff entries that represent the conflicts that
+ * have not been automerged. These items will be written to high-stage
+ * entries in the main index.
+ */
+ git_vector conflicts;
+
+ /* Vector of git_merge_diff that have been automerged. These items
+ * will be written to the REUC when the index is produced.
+ */
+ git_vector resolved;
+} git_merge_diff_list;
+
+/**
+ * Description of changes to one file across three trees.
+ */
+typedef struct {
+ git_merge_diff_type_t type;
+
+ git_index_entry ancestor_entry;
+
+ git_index_entry our_entry;
+ git_delta_t our_status;
+
+ git_index_entry their_entry;
+ git_delta_t their_status;
+
+} git_merge_diff;
+
+int git_merge__bases_many(
+ git_commit_list **out,
+ git_revwalk *walk,
+ git_commit_list_node *one,
+ git_vector *twos);
+
+/*
+ * Three-way tree differencing
+ */
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);
+
+int git_merge_diff_list__find_differences(
+ git_merge_diff_list *merge_diff_list,
+ git_iterator *ancestor_iterator,
+ git_iterator *ours_iter,
+ git_iterator *theirs_iter);
+
+int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts);
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list);
+
+/* Merge metadata setup */
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_annotated_commit *our_head,
+ const git_annotated_commit *heads[],
+ size_t heads_len);
+
+int git_merge__iterators(
+ git_index **out,
+ git_repository *repo,
+ git_iterator *ancestor_iter,
+ git_iterator *our_iter,
+ git_iterator *their_iter,
+ const git_merge_options *given_opts);
+
+int git_merge__check_result(git_repository *repo, git_index *index_new);
+
+int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index);
+
+/* Merge files */
+
+GIT_INLINE(const char *) git_merge_file__best_path(
+ const char *ancestor,
+ const char *ours,
+ const char *theirs)
+{
+ if (!ancestor) {
+ if (ours && theirs && strcmp(ours, theirs) == 0)
+ return ours;
+
+ return NULL;
+ }
+
+ if (ours && strcmp(ancestor, ours) == 0)
+ return theirs;
+ else if(theirs && strcmp(ancestor, theirs) == 0)
+ return ours;
+
+ return NULL;
+}
+
+GIT_INLINE(uint32_t) git_merge_file__best_mode(
+ uint32_t ancestor, uint32_t ours, uint32_t theirs)
+{
+ /*
+ * If ancestor didn't exist and either ours or theirs is executable,
+ * assume executable. Otherwise, if any mode changed from the ancestor,
+ * use that one.
+ */
+ if (!ancestor) {
+ if (ours == GIT_FILEMODE_BLOB_EXECUTABLE ||
+ theirs == GIT_FILEMODE_BLOB_EXECUTABLE)
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ return GIT_FILEMODE_BLOB;
+ } else if (ours && theirs) {
+ if (ancestor == ours)
+ return theirs;
+
+ return ours;
+ }
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "vector.h"
+#include "global.h"
+#include "merge.h"
+#include "merge_driver.h"
+#include "git2/merge.h"
+#include "git2/sys/merge.h"
+
+static const char *merge_driver_name__text = "text";
+static const char *merge_driver_name__union = "union";
+static const char *merge_driver_name__binary = "binary";
+
+struct merge_driver_registry {
+ git_rwlock lock;
+ git_vector drivers;
+};
+
+typedef struct {
+ git_merge_driver *driver;
+ int initialized;
+ char name[GIT_FLEX_ARRAY];
+} git_merge_driver_entry;
+
+static struct merge_driver_registry merge_driver_registry;
+
+static void git_merge_driver_global_shutdown(void);
+
+
+int git_merge_driver__builtin_apply(
+ git_merge_driver *self,
+ const char **path_out,
+ uint32_t *mode_out,
+ git_buf *merged_out,
+ const char *filter_name,
+ const git_merge_driver_source *src)
+{
+ git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self;
+ git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ git_merge_file_result result = {0};
+ int error;
+
+ GIT_UNUSED(filter_name);
+
+ if (src->file_opts)
+ memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options));
+
+ if (driver->favor)
+ file_opts.favor = driver->favor;
+
+ if ((error = git_merge_file_from_index(&result, src->repo,
+ src->ancestor, src->ours, src->theirs, &file_opts)) < 0)
+ goto done;
+
+ if (!result.automergeable &&
+ !(file_opts.flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) {
+ error = GIT_EMERGECONFLICT;
+ goto done;
+ }
+
+ *path_out = git_merge_file__best_path(
+ src->ancestor ? src->ancestor->path : NULL,
+ src->ours ? src->ours->path : NULL,
+ src->theirs ? src->theirs->path : NULL);
+
+ *mode_out = git_merge_file__best_mode(
+ src->ancestor ? src->ancestor->mode : 0,
+ src->ours ? src->ours->mode : 0,
+ src->theirs ? src->theirs->mode : 0);
+
+ merged_out->ptr = (char *)result.ptr;
+ merged_out->size = result.len;
+ merged_out->asize = result.len;
+ result.ptr = NULL;
+
+done:
+ git_merge_file_result_free(&result);
+ return error;
+}
+
+static int merge_driver_binary_apply(
+ git_merge_driver *self,
+ const char **path_out,
+ uint32_t *mode_out,
+ git_buf *merged_out,
+ const char *filter_name,
+ const git_merge_driver_source *src)
+{
+ GIT_UNUSED(self);
+ GIT_UNUSED(path_out);
+ GIT_UNUSED(mode_out);
+ GIT_UNUSED(merged_out);
+ GIT_UNUSED(filter_name);
+ GIT_UNUSED(src);
+
+ return GIT_EMERGECONFLICT;
+}
+
+static int merge_driver_entry_cmp(const void *a, const void *b)
+{
+ const git_merge_driver_entry *entry_a = a;
+ const git_merge_driver_entry *entry_b = b;
+
+ return strcmp(entry_a->name, entry_b->name);
+}
+
+static int merge_driver_entry_search(const void *a, const void *b)
+{
+ const char *name_a = a;
+ const git_merge_driver_entry *entry_b = b;
+
+ return strcmp(name_a, entry_b->name);
+}
+
+git_merge_driver__builtin git_merge_driver__text = {
+ {
+ GIT_MERGE_DRIVER_VERSION,
+ NULL,
+ NULL,
+ git_merge_driver__builtin_apply,
+ },
+ GIT_MERGE_FILE_FAVOR_NORMAL
+};
+
+git_merge_driver__builtin git_merge_driver__union = {
+ {
+ GIT_MERGE_DRIVER_VERSION,
+ NULL,
+ NULL,
+ git_merge_driver__builtin_apply,
+ },
+ GIT_MERGE_FILE_FAVOR_UNION
+};
+
+git_merge_driver git_merge_driver__binary = {
+ GIT_MERGE_DRIVER_VERSION,
+ NULL,
+ NULL,
+ merge_driver_binary_apply
+};
+
+/* Note: callers must lock the registry before calling this function */
+static int merge_driver_registry_insert(
+ const char *name, git_merge_driver *driver)
+{
+ git_merge_driver_entry *entry;
+
+ entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1);
+ GITERR_CHECK_ALLOC(entry);
+
+ strcpy(entry->name, name);
+ entry->driver = driver;
+
+ return git_vector_insert_sorted(
+ &merge_driver_registry.drivers, entry, NULL);
+}
+
+int git_merge_driver_global_init(void)
+{
+ int error;
+
+ if (git_rwlock_init(&merge_driver_registry.lock) < 0)
+ return -1;
+
+ if ((error = git_vector_init(&merge_driver_registry.drivers, 3,
+ merge_driver_entry_cmp)) < 0)
+ goto done;
+
+ if ((error = merge_driver_registry_insert(
+ merge_driver_name__text, &git_merge_driver__text.base)) < 0 ||
+ (error = merge_driver_registry_insert(
+ merge_driver_name__union, &git_merge_driver__union.base)) < 0 ||
+ (error = merge_driver_registry_insert(
+ merge_driver_name__binary, &git_merge_driver__binary)) < 0)
+ goto done;
+
+ git__on_shutdown(git_merge_driver_global_shutdown);
+
+done:
+ if (error < 0)
+ git_vector_free_deep(&merge_driver_registry.drivers);
+
+ return error;
+}
+
+static void git_merge_driver_global_shutdown(void)
+{
+ git_merge_driver_entry *entry;
+ size_t i;
+
+ if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0)
+ return;
+
+ git_vector_foreach(&merge_driver_registry.drivers, i, entry) {
+ if (entry->driver->shutdown)
+ entry->driver->shutdown(entry->driver);
+
+ git__free(entry);
+ }
+
+ git_vector_free(&merge_driver_registry.drivers);
+
+ git_rwlock_wrunlock(&merge_driver_registry.lock);
+ git_rwlock_free(&merge_driver_registry.lock);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static int merge_driver_registry_find(size_t *pos, const char *name)
+{
+ return git_vector_search2(pos, &merge_driver_registry.drivers,
+ merge_driver_entry_search, name);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static git_merge_driver_entry *merge_driver_registry_lookup(
+ size_t *pos, const char *name)
+{
+ git_merge_driver_entry *entry = NULL;
+
+ if (!merge_driver_registry_find(pos, name))
+ entry = git_vector_get(&merge_driver_registry.drivers, *pos);
+
+ return entry;
+}
+
+int git_merge_driver_register(const char *name, git_merge_driver *driver)
+{
+ int error;
+
+ assert(name && driver);
+
+ if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock merge driver registry");
+ return -1;
+ }
+
+ if (!merge_driver_registry_find(NULL, name)) {
+ giterr_set(GITERR_MERGE, "attempt to reregister existing driver '%s'",
+ name);
+ error = GIT_EEXISTS;
+ goto done;
+ }
+
+ error = merge_driver_registry_insert(name, driver);
+
+done:
+ git_rwlock_wrunlock(&merge_driver_registry.lock);
+ return error;
+}
+
+int git_merge_driver_unregister(const char *name)
+{
+ git_merge_driver_entry *entry;
+ size_t pos;
+ int error = 0;
+
+ if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock merge driver registry");
+ return -1;
+ }
+
+ if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) {
+ giterr_set(GITERR_MERGE, "cannot find merge driver '%s' to unregister",
+ name);
+ error = GIT_ENOTFOUND;
+ goto done;
+ }
+
+ git_vector_remove(&merge_driver_registry.drivers, pos);
+
+ if (entry->initialized && entry->driver->shutdown) {
+ entry->driver->shutdown(entry->driver);
+ entry->initialized = false;
+ }
+
+ git__free(entry);
+
+done:
+ git_rwlock_wrunlock(&merge_driver_registry.lock);
+ return error;
+}
+
+git_merge_driver *git_merge_driver_lookup(const char *name)
+{
+ git_merge_driver_entry *entry;
+ size_t pos;
+ int error;
+
+ /* If we've decided the merge driver to use internally - and not
+ * based on user configuration (in merge_driver_name_for_path)
+ * then we can use a hardcoded name to compare instead of bothering
+ * to take a lock and look it up in the vector.
+ */
+ if (name == merge_driver_name__text)
+ return &git_merge_driver__text.base;
+ else if (name == merge_driver_name__binary)
+ return &git_merge_driver__binary;
+
+ if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock merge driver registry");
+ return NULL;
+ }
+
+ entry = merge_driver_registry_lookup(&pos, name);
+
+ git_rwlock_rdunlock(&merge_driver_registry.lock);
+
+ if (entry == NULL) {
+ giterr_set(GITERR_MERGE, "cannot use an unregistered filter");
+ return NULL;
+ }
+
+ if (!entry->initialized) {
+ if (entry->driver->initialize &&
+ (error = entry->driver->initialize(entry->driver)) < 0)
+ return NULL;
+
+ entry->initialized = 1;
+ }
+
+ return entry->driver;
+}
+
+static int merge_driver_name_for_path(
+ const char **out,
+ git_repository *repo,
+ const char *path,
+ const char *default_driver)
+{
+ const char *value;
+ int error;
+
+ *out = NULL;
+
+ if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0)
+ return error;
+
+ /* set: use the built-in 3-way merge driver ("text") */
+ if (GIT_ATTR_TRUE(value))
+ *out = merge_driver_name__text;
+
+ /* unset: do not merge ("binary") */
+ else if (GIT_ATTR_FALSE(value))
+ *out = merge_driver_name__binary;
+
+ else if (GIT_ATTR_UNSPECIFIED(value) && default_driver)
+ *out = default_driver;
+
+ else if (GIT_ATTR_UNSPECIFIED(value))
+ *out = merge_driver_name__text;
+
+ else
+ *out = value;
+
+ return 0;
+}
+
+
+GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard(
+ const char *name)
+{
+ git_merge_driver *driver = git_merge_driver_lookup(name);
+
+ if (driver == NULL)
+ driver = git_merge_driver_lookup("*");
+
+ return driver;
+}
+
+int git_merge_driver_for_source(
+ const char **name_out,
+ git_merge_driver **driver_out,
+ const git_merge_driver_source *src)
+{
+ const char *path, *driver_name;
+ int error = 0;
+
+ path = git_merge_file__best_path(
+ src->ancestor ? src->ancestor->path : NULL,
+ src->ours ? src->ours->path : NULL,
+ src->theirs ? src->theirs->path : NULL);
+
+ if ((error = merge_driver_name_for_path(
+ &driver_name, src->repo, path, src->default_driver)) < 0)
+ return error;
+
+ *name_out = driver_name;
+ *driver_out = merge_driver_lookup_with_wildcard(driver_name);
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_merge_driver_h__
+#define INCLUDE_merge_driver_h__
+
+#include "git2/merge.h"
+#include "git2/index.h"
+#include "git2/sys/merge.h"
+
+struct git_merge_driver_source {
+ git_repository *repo;
+ const char *default_driver;
+ const git_merge_file_options *file_opts;
+
+ const git_index_entry *ancestor;
+ const git_index_entry *ours;
+ const git_index_entry *theirs;
+};
+
+typedef struct git_merge_driver__builtin {
+ git_merge_driver base;
+ git_merge_file_favor_t favor;
+} git_merge_driver__builtin;
+
+extern int git_merge_driver_global_init(void);
+
+extern int git_merge_driver_for_path(
+ char **name_out,
+ git_merge_driver **driver_out,
+ git_repository *repo,
+ const char *path);
+
+/* Merge driver configuration */
+extern int git_merge_driver_for_source(
+ const char **name_out,
+ git_merge_driver **driver_out,
+ const git_merge_driver_source *src);
+
+extern int git_merge_driver__builtin_apply(
+ git_merge_driver *self,
+ const char **path_out,
+ uint32_t *mode_out,
+ git_buf *merged_out,
+ const char *filter_name,
+ const git_merge_driver_source *src);
+
+/* Merge driver for text files, performs a standard three-way merge */
+extern git_merge_driver__builtin git_merge_driver__text;
+
+/* Merge driver for union-style merging */
+extern git_merge_driver__builtin git_merge_driver__union;
+
+/* Merge driver for unmergeable (binary) files: always produces conflicts */
+extern git_merge_driver git_merge_driver__binary;
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "posix.h"
+#include "fileops.h"
+#include "index.h"
+#include "diff_xdiff.h"
+#include "merge.h"
+
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/index.h"
+#include "git2/merge.h"
+
+#include "xdiff/xdiff.h"
+
+/* only examine the first 8000 bytes for binaryness.
+ * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197
+ */
+#define GIT_MERGE_FILE_BINARY_SIZE 8000
+
+#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0)
+
+int git_merge_file__input_from_index(
+ git_merge_file_input *input_out,
+ git_odb_object **odb_object_out,
+ git_odb *odb,
+ const git_index_entry *entry)
+{
+ int error = 0;
+
+ assert(input_out && odb_object_out && odb && entry);
+
+ if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
+ goto done;
+
+ input_out->path = entry->path;
+ input_out->mode = entry->mode;
+ input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
+ input_out->size = git_odb_object_size(*odb_object_out);
+
+done:
+ return error;
+}
+
+static void merge_file_normalize_opts(
+ git_merge_file_options *out,
+ const git_merge_file_options *given_opts)
+{
+ if (given_opts)
+ memcpy(out, given_opts, sizeof(git_merge_file_options));
+ else {
+ git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ memcpy(out, &default_opts, sizeof(git_merge_file_options));
+ }
+}
+
+static int merge_file__xdiff(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *given_opts)
+{
+ xmparam_t xmparam;
+ mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
+ mmbuffer_t mmbuffer;
+ git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
+ const char *path;
+ int xdl_result;
+ int error = 0;
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ merge_file_normalize_opts(&options, given_opts);
+
+ memset(&xmparam, 0x0, sizeof(xmparam_t));
+
+ if (ancestor) {
+ xmparam.ancestor = (options.ancestor_label) ?
+ options.ancestor_label : ancestor->path;
+ ancestor_mmfile.ptr = (char *)ancestor->ptr;
+ ancestor_mmfile.size = ancestor->size;
+ }
+
+ xmparam.file1 = (options.our_label) ?
+ options.our_label : ours->path;
+ our_mmfile.ptr = (char *)ours->ptr;
+ our_mmfile.size = ours->size;
+
+ xmparam.file2 = (options.their_label) ?
+ options.their_label : theirs->path;
+ their_mmfile.ptr = (char *)theirs->ptr;
+ their_mmfile.size = theirs->size;
+
+ if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
+ xmparam.favor = XDL_MERGE_FAVOR_OURS;
+ else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
+ xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
+ else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
+ xmparam.favor = XDL_MERGE_FAVOR_UNION;
+
+ xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
+ XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
+
+ if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
+ xmparam.style = XDL_MERGE_DIFF3;
+
+ if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
+ xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
+ if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
+ xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
+ if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
+ xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+ if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE)
+ xmparam.xpp.flags |= XDF_PATIENCE_DIFF;
+
+ if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL)
+ xmparam.xpp.flags |= XDF_NEED_MINIMAL;
+
+ if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
+ &their_mmfile, &xmparam, &mmbuffer)) < 0) {
+ giterr_set(GITERR_MERGE, "Failed to merge files.");
+ error = -1;
+ goto done;
+ }
+
+ path = git_merge_file__best_path(
+ ancestor ? ancestor->path : NULL,
+ ours->path,
+ theirs->path);
+
+ if (path != NULL && (out->path = git__strdup(path)) == NULL) {
+ error = -1;
+ goto done;
+ }
+
+ out->automergeable = (xdl_result == 0);
+ out->ptr = (const char *)mmbuffer.ptr;
+ out->len = mmbuffer.size;
+ out->mode = git_merge_file__best_mode(
+ ancestor ? ancestor->mode : 0,
+ ours->mode,
+ theirs->mode);
+
+done:
+ if (error < 0)
+ git_merge_file_result_free(out);
+
+ return error;
+}
+
+static bool merge_file__is_binary(const git_merge_file_input *file)
+{
+ size_t len = file ? file->size : 0;
+
+ if (len > GIT_XDIFF_MAX_SIZE)
+ return true;
+ if (len > GIT_MERGE_FILE_BINARY_SIZE)
+ len = GIT_MERGE_FILE_BINARY_SIZE;
+
+ return len ? (memchr(file->ptr, 0, len) != NULL) : false;
+}
+
+static int merge_file__binary(
+ git_merge_file_result *out,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *given_opts)
+{
+ const git_merge_file_input *favored = NULL;
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS)
+ favored = ours;
+ else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS)
+ favored = theirs;
+ else
+ goto done;
+
+ if ((out->path = git__strdup(favored->path)) == NULL ||
+ (out->ptr = git__malloc(favored->size)) == NULL)
+ goto done;
+
+ memcpy((char *)out->ptr, favored->ptr, favored->size);
+ out->len = favored->size;
+ out->mode = favored->mode;
+ out->automergeable = 1;
+
+done:
+ return 0;
+}
+
+static int merge_file__from_inputs(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *given_opts)
+{
+ if (merge_file__is_binary(ancestor) ||
+ merge_file__is_binary(ours) ||
+ merge_file__is_binary(theirs))
+ return merge_file__binary(out, ours, theirs, given_opts);
+
+ return merge_file__xdiff(out, ancestor, ours, theirs, given_opts);
+}
+
+static git_merge_file_input *git_merge_file__normalize_inputs(
+ git_merge_file_input *out,
+ const git_merge_file_input *given)
+{
+ memcpy(out, given, sizeof(git_merge_file_input));
+
+ if (!out->path)
+ out->path = "file.txt";
+
+ if (!out->mode)
+ out->mode = 0100644;
+
+ return out;
+}
+
+int git_merge_file(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *options)
+{
+ git_merge_file_input inputs[3] = { {0} };
+
+ assert(out && ours && theirs);
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if (ancestor)
+ ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
+
+ ours = git_merge_file__normalize_inputs(&inputs[1], ours);
+ theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
+
+ return merge_file__from_inputs(out, ancestor, ours, theirs, options);
+}
+
+int git_merge_file_from_index(
+ git_merge_file_result *out,
+ git_repository *repo,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ const git_merge_file_options *options)
+{
+ git_merge_file_input *ancestor_ptr = NULL,
+ ancestor_input = {0}, our_input = {0}, their_input = {0};
+ git_odb *odb = NULL;
+ git_odb_object *odb_object[3] = { 0 };
+ int error = 0;
+
+ assert(out && repo && ours && theirs);
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ goto done;
+
+ if (ancestor) {
+ if ((error = git_merge_file__input_from_index(
+ &ancestor_input, &odb_object[0], odb, ancestor)) < 0)
+ goto done;
+
+ ancestor_ptr = &ancestor_input;
+ }
+
+ if ((error = git_merge_file__input_from_index(
+ &our_input, &odb_object[1], odb, ours)) < 0 ||
+ (error = git_merge_file__input_from_index(
+ &their_input, &odb_object[2], odb, theirs)) < 0)
+ goto done;
+
+ error = merge_file__from_inputs(out,
+ ancestor_ptr, &our_input, &their_input, options);
+
+done:
+ git_odb_object_free(odb_object[0]);
+ git_odb_object_free(odb_object[1]);
+ git_odb_object_free(odb_object[2]);
+ git_odb_free(odb);
+
+ return error;
+}
+
+void git_merge_file_result_free(git_merge_file_result *result)
+{
+ if (result == NULL)
+ return;
+
+ git__free((char *)result->path);
+ git__free((char *)result->ptr);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "message.h"
+
+static size_t line_length_without_trailing_spaces(const char *line, size_t len)
+{
+ while (len) {
+ unsigned char c = line[len - 1];
+ if (!git__isspace(c))
+ break;
+ len--;
+ }
+
+ return len;
+}
+
+/* Greatly inspired from git.git "stripspace" */
+/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */
+int git_message_prettify(git_buf *message_out, const char *message, int strip_comments, char comment_char)
+{
+ const size_t message_len = strlen(message);
+
+ int consecutive_empty_lines = 0;
+ size_t i, line_length, rtrimmed_line_length;
+ char *next_newline;
+
+ git_buf_sanitize(message_out);
+
+ for (i = 0; i < strlen(message); i += line_length) {
+ next_newline = memchr(message + i, '\n', message_len - i);
+
+ if (next_newline != NULL) {
+ line_length = next_newline - (message + i) + 1;
+ } else {
+ line_length = message_len - i;
+ }
+
+ if (strip_comments && line_length && message[i] == comment_char)
+ continue;
+
+ rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length);
+
+ if (!rtrimmed_line_length) {
+ consecutive_empty_lines++;
+ continue;
+ }
+
+ if (consecutive_empty_lines > 0 && message_out->size > 0)
+ git_buf_putc(message_out, '\n');
+
+ consecutive_empty_lines = 0;
+ git_buf_put(message_out, message + i, rtrimmed_line_length);
+ git_buf_putc(message_out, '\n');
+ }
+
+ return git_buf_oom(message_out) ? -1 : 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_message_h__
+#define INCLUDE_message_h__
+
+#include "git2/message.h"
+#include "buffer.h"
+
+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments);
+
+#endif /* INCLUDE_message_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "mwindow.h"
+#include "vector.h"
+#include "fileops.h"
+#include "map.h"
+#include "global.h"
+#include "strmap.h"
+#include "pack.h"
+
+GIT__USE_STRMAP
+
+#define DEFAULT_WINDOW_SIZE \
+ (sizeof(void*) >= 8 \
+ ? 1 * 1024 * 1024 * 1024 \
+ : 32 * 1024 * 1024)
+
+#define DEFAULT_MAPPED_LIMIT \
+ ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
+
+size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
+size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
+
+/* Whenever you want to read or modify this, grab git__mwindow_mutex */
+static git_mwindow_ctl mem_ctl;
+
+/* Global list of mwindow files, to open packs once across repos */
+git_strmap *git__pack_cache = NULL;
+
+static void git_mwindow_files_free(void)
+{
+ git_strmap *tmp = git__pack_cache;
+
+ git__pack_cache = NULL;
+ git_strmap_free(tmp);
+}
+
+int git_mwindow_global_init(void)
+{
+ assert(!git__pack_cache);
+
+ git__on_shutdown(git_mwindow_files_free);
+ return git_strmap_alloc(&git__pack_cache);
+}
+
+int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
+{
+ int error;
+ char *packname;
+ git_strmap_iter pos;
+ struct git_pack_file *pack;
+
+ if ((error = git_packfile__name(&packname, path)) < 0)
+ return error;
+
+ if (git_mutex_lock(&git__mwindow_mutex) < 0) {
+ giterr_set(GITERR_OS, "failed to lock mwindow mutex");
+ return -1;
+ }
+
+ pos = git_strmap_lookup_index(git__pack_cache, packname);
+ git__free(packname);
+
+ if (git_strmap_valid_index(git__pack_cache, pos)) {
+ pack = git_strmap_value_at(git__pack_cache, pos);
+ git_atomic_inc(&pack->refcount);
+
+ git_mutex_unlock(&git__mwindow_mutex);
+ *out = pack;
+ return 0;
+ }
+
+ /* If we didn't find it, we need to create it */
+ if ((error = git_packfile_alloc(&pack, path)) < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ return error;
+ }
+
+ git_atomic_inc(&pack->refcount);
+
+ git_strmap_insert(git__pack_cache, pack->pack_name, pack, error);
+ git_mutex_unlock(&git__mwindow_mutex);
+
+ if (error < 0) {
+ git_packfile_free(pack);
+ return -1;
+ }
+
+ *out = pack;
+ return 0;
+}
+
+void git_mwindow_put_pack(struct git_pack_file *pack)
+{
+ int count;
+ git_strmap_iter pos;
+
+ if (git_mutex_lock(&git__mwindow_mutex) < 0)
+ return;
+
+ /* put before get would be a corrupted state */
+ assert(git__pack_cache);
+
+ pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name);
+ /* if we cannot find it, the state is corrupted */
+ assert(git_strmap_valid_index(git__pack_cache, pos));
+
+ count = git_atomic_dec(&pack->refcount);
+ if (count == 0) {
+ git_strmap_delete_at(git__pack_cache, pos);
+ git_packfile_free(pack);
+ }
+
+ git_mutex_unlock(&git__mwindow_mutex);
+ return;
+}
+
+void git_mwindow_free_all(git_mwindow_file *mwf)
+{
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
+ git_mwindow_free_all_locked(mwf);
+
+ git_mutex_unlock(&git__mwindow_mutex);
+}
+
+/*
+ * Free all the windows in a sequence, typically because we're done
+ * with the file
+ */
+void git_mwindow_free_all_locked(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+
+ /*
+ * Remove these windows from the global list
+ */
+ for (i = 0; i < ctl->windowfiles.length; ++i){
+ if (git_vector_get(&ctl->windowfiles, i) == mwf) {
+ git_vector_remove(&ctl->windowfiles, i);
+ break;
+ }
+ }
+
+ if (ctl->windowfiles.length == 0) {
+ git_vector_free(&ctl->windowfiles);
+ ctl->windowfiles.contents = NULL;
+ }
+
+ while (mwf->windows) {
+ git_mwindow *w = mwf->windows;
+ assert(w->inuse_cnt == 0);
+
+ ctl->mapped -= w->window_map.len;
+ ctl->open_windows--;
+
+ git_futils_mmap_free(&w->window_map);
+
+ mwf->windows = w->next;
+ git__free(w);
+ }
+}
+
+/*
+ * Check if a window 'win' contains the address 'offset'
+ */
+int git_mwindow_contains(git_mwindow *win, git_off_t offset)
+{
+ git_off_t win_off = win->offset;
+ return win_off <= offset
+ && offset <= (git_off_t)(win_off + win->window_map.len);
+}
+
+/*
+ * Find the least-recently-used window in a file
+ */
+static void git_mwindow_scan_lru(
+ git_mwindow_file *mwf,
+ git_mwindow **lru_w,
+ git_mwindow **lru_l)
+{
+ git_mwindow *w, *w_l;
+
+ for (w_l = NULL, w = mwf->windows; w; w = w->next) {
+ if (!w->inuse_cnt) {
+ /*
+ * If the current one is more recent than the last one,
+ * store it in the output parameter. If lru_w is NULL,
+ * it's the first loop, so store it as well.
+ */
+ if (!*lru_w || w->last_used < (*lru_w)->last_used) {
+ *lru_w = w;
+ *lru_l = w_l;
+ }
+ }
+ w_l = w;
+ }
+}
+
+/*
+ * Close the least recently used window. You should check to see if
+ * the file descriptors need closing from time to time. Called under
+ * lock from new_window.
+ */
+static int git_mwindow_close_lru(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+ git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
+
+ /* FIXME: Does this give us any advantage? */
+ if(mwf->windows)
+ git_mwindow_scan_lru(mwf, &lru_w, &lru_l);
+
+ for (i = 0; i < ctl->windowfiles.length; ++i) {
+ git_mwindow *last = lru_w;
+ git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i);
+ git_mwindow_scan_lru(cur, &lru_w, &lru_l);
+ if (lru_w != last)
+ list = &cur->windows;
+ }
+
+ if (!lru_w) {
+ giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU");
+ return -1;
+ }
+
+ ctl->mapped -= lru_w->window_map.len;
+ git_futils_mmap_free(&lru_w->window_map);
+
+ if (lru_l)
+ lru_l->next = lru_w->next;
+ else
+ *list = lru_w->next;
+
+ git__free(lru_w);
+ ctl->open_windows--;
+
+ return 0;
+}
+
+/* This gets called under lock from git_mwindow_open */
+static git_mwindow *new_window(
+ git_mwindow_file *mwf,
+ git_file fd,
+ git_off_t size,
+ git_off_t offset)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t walign = git_mwindow__window_size / 2;
+ git_off_t len;
+ git_mwindow *w;
+
+ w = git__malloc(sizeof(*w));
+
+ if (w == NULL)
+ return NULL;
+
+ memset(w, 0x0, sizeof(*w));
+ w->offset = (offset / walign) * walign;
+
+ len = size - w->offset;
+ if (len > (git_off_t)git_mwindow__window_size)
+ len = (git_off_t)git_mwindow__window_size;
+
+ ctl->mapped += (size_t)len;
+
+ while (git_mwindow__mapped_limit < ctl->mapped &&
+ git_mwindow_close_lru(mwf) == 0) /* nop */;
+
+ /*
+ * We treat `mapped_limit` as a soft limit. If we can't find a
+ * window to close and are above the limit, we still mmap the new
+ * window.
+ */
+
+ if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
+ /*
+ * The first error might be down to memory fragmentation even if
+ * we're below our soft limits, so free up what we can and try again.
+ */
+
+ while (git_mwindow_close_lru(mwf) == 0)
+ /* nop */;
+
+ if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
+ git__free(w);
+ return NULL;
+ }
+ }
+
+ ctl->mmap_calls++;
+ ctl->open_windows++;
+
+ if (ctl->mapped > ctl->peak_mapped)
+ ctl->peak_mapped = ctl->mapped;
+
+ if (ctl->open_windows > ctl->peak_open_windows)
+ ctl->peak_open_windows = ctl->open_windows;
+
+ return w;
+}
+
+/*
+ * Open a new window, closing the least recenty used until we have
+ * enough space. Don't forget to add it to your list
+ */
+unsigned char *git_mwindow_open(
+ git_mwindow_file *mwf,
+ git_mwindow **cursor,
+ git_off_t offset,
+ size_t extra,
+ unsigned int *left)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ git_mwindow *w = *cursor;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return NULL;
+ }
+
+ if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
+ if (w) {
+ w->inuse_cnt--;
+ }
+
+ for (w = mwf->windows; w; w = w->next) {
+ if (git_mwindow_contains(w, offset) &&
+ git_mwindow_contains(w, offset + extra))
+ break;
+ }
+
+ /*
+ * If there isn't a suitable window, we need to create a new
+ * one.
+ */
+ if (!w) {
+ w = new_window(mwf, mwf->fd, mwf->size, offset);
+ if (w == NULL) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ return NULL;
+ }
+ w->next = mwf->windows;
+ mwf->windows = w;
+ }
+ }
+
+ /* If we changed w, store it in the cursor */
+ if (w != *cursor) {
+ w->last_used = ctl->used_ctr++;
+ w->inuse_cnt++;
+ *cursor = w;
+ }
+
+ offset -= w->offset;
+
+ if (left)
+ *left = (unsigned int)(w->window_map.len - offset);
+
+ git_mutex_unlock(&git__mwindow_mutex);
+ return (unsigned char *) w->window_map.data + offset;
+}
+
+int git_mwindow_file_register(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ int ret;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return -1;
+ }
+
+ if (ctl->windowfiles.length == 0 &&
+ git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ return -1;
+ }
+
+ ret = git_vector_insert(&ctl->windowfiles, mwf);
+ git_mutex_unlock(&git__mwindow_mutex);
+
+ return ret;
+}
+
+void git_mwindow_file_deregister(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ git_mwindow_file *cur;
+ size_t i;
+
+ if (git_mutex_lock(&git__mwindow_mutex))
+ return;
+
+ git_vector_foreach(&ctl->windowfiles, i, cur) {
+ if (cur == mwf) {
+ git_vector_remove(&ctl->windowfiles, i);
+ git_mutex_unlock(&git__mwindow_mutex);
+ return;
+ }
+ }
+ git_mutex_unlock(&git__mwindow_mutex);
+}
+
+void git_mwindow_close(git_mwindow **window)
+{
+ git_mwindow *w = *window;
+ if (w) {
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
+ w->inuse_cnt--;
+ git_mutex_unlock(&git__mwindow_mutex);
+ *window = NULL;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_mwindow__
+#define INCLUDE_mwindow__
+
+#include "map.h"
+#include "vector.h"
+
+typedef struct git_mwindow {
+ struct git_mwindow *next;
+ git_map window_map;
+ git_off_t offset;
+ size_t last_used;
+ size_t inuse_cnt;
+} git_mwindow;
+
+typedef struct git_mwindow_file {
+ git_mwindow *windows;
+ int fd;
+ git_off_t size;
+} git_mwindow_file;
+
+typedef struct git_mwindow_ctl {
+ size_t mapped;
+ unsigned int open_windows;
+ unsigned int mmap_calls;
+ unsigned int peak_open_windows;
+ size_t peak_mapped;
+ size_t used_ctr;
+ git_vector windowfiles;
+} git_mwindow_ctl;
+
+int git_mwindow_contains(git_mwindow *win, git_off_t offset);
+void git_mwindow_free_all(git_mwindow_file *mwf); /* locks */
+void git_mwindow_free_all_locked(git_mwindow_file *mwf); /* run under lock */
+unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left);
+int git_mwindow_file_register(git_mwindow_file *mwf);
+void git_mwindow_file_deregister(git_mwindow_file *mwf);
+void git_mwindow_close(git_mwindow **w_cursor);
+
+extern int git_mwindow_global_init(void);
+
+struct git_pack_file; /* just declaration to avoid cyclical includes */
+int git_mwindow_get_pack(struct git_pack_file **out, const char *path);
+void git_mwindow_put_pack(struct git_pack_file *pack);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <ctype.h>
+#include "git2/errors.h"
+
+#include "common.h"
+#include "netops.h"
+#include "posix.h"
+#include "buffer.h"
+#include "http_parser.h"
+#include "global.h"
+
+int gitno_recv(gitno_buffer *buf)
+{
+ return buf->recv(buf);
+}
+
+void gitno_buffer_setup_callback(
+ gitno_buffer *buf,
+ char *data,
+ size_t len,
+ int (*recv)(gitno_buffer *buf), void *cb_data)
+{
+ memset(data, 0x0, len);
+ buf->data = data;
+ buf->len = len;
+ buf->offset = 0;
+ buf->recv = recv;
+ buf->cb_data = cb_data;
+}
+
+static int recv_stream(gitno_buffer *buf)
+{
+ git_stream *io = (git_stream *) buf->cb_data;
+ int ret;
+
+ ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset);
+ if (ret < 0)
+ return -1;
+
+ buf->offset += ret;
+ return ret;
+}
+
+void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len)
+{
+ memset(data, 0x0, len);
+ buf->data = data;
+ buf->len = len;
+ buf->offset = 0;
+ buf->recv = recv_stream;
+ buf->cb_data = st;
+}
+
+/* Consume up to ptr and move the rest of the buffer to the beginning */
+void gitno_consume(gitno_buffer *buf, const char *ptr)
+{
+ size_t consumed;
+
+ assert(ptr - buf->data >= 0);
+ assert(ptr - buf->data <= (int) buf->len);
+
+ consumed = ptr - buf->data;
+
+ memmove(buf->data, ptr, buf->offset - consumed);
+ memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
+ buf->offset -= consumed;
+}
+
+/* Consume const bytes and move the rest of the buffer to the beginning */
+void gitno_consume_n(gitno_buffer *buf, size_t cons)
+{
+ memmove(buf->data, buf->data + cons, buf->len - buf->offset);
+ memset(buf->data + cons, 0x0, buf->len - buf->offset);
+ buf->offset -= cons;
+}
+
+/* Match host names according to RFC 2818 rules */
+int gitno__match_host(const char *pattern, const char *host)
+{
+ for (;;) {
+ char c = git__tolower(*pattern++);
+
+ if (c == '\0')
+ return *host ? -1 : 0;
+
+ if (c == '*') {
+ c = *pattern;
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return 0;
+
+ /*
+ * We've found a pattern, so move towards the next matching
+ * char. The '.' is handled specially because wildcards aren't
+ * allowed to cross subdomains.
+ */
+
+ while(*host) {
+ char h = git__tolower(*host);
+ if (c == h)
+ return gitno__match_host(pattern, host++);
+ if (h == '.')
+ return gitno__match_host(pattern, host);
+ host++;
+ }
+ return -1;
+ }
+
+ if (c != git__tolower(*host++))
+ return -1;
+ }
+
+ return -1;
+}
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+
+int gitno_connection_data_from_url(
+ gitno_connection_data *data,
+ const char *url,
+ const char *service_suffix)
+{
+ int error = -1;
+ const char *default_port = NULL, *path_search_start = NULL;
+ char *original_host = NULL;
+
+ /* service_suffix is optional */
+ assert(data && url);
+
+ /* Save these for comparison later */
+ original_host = data->host;
+ data->host = NULL;
+ gitno_connection_data_free_ptrs(data);
+
+ if (!git__prefixcmp(url, prefix_http)) {
+ path_search_start = url + strlen(prefix_http);
+ default_port = "80";
+
+ if (data->use_ssl) {
+ giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed");
+ goto cleanup;
+ }
+ } else if (!git__prefixcmp(url, prefix_https)) {
+ path_search_start = url + strlen(prefix_https);
+ default_port = "443";
+ data->use_ssl = true;
+ } else if (url[0] == '/')
+ default_port = data->use_ssl ? "443" : "80";
+
+ if (!default_port) {
+ giterr_set(GITERR_NET, "Unrecognized URL prefix");
+ goto cleanup;
+ }
+
+ error = gitno_extract_url_parts(
+ &data->host, &data->port, &data->path, &data->user, &data->pass,
+ url, default_port);
+
+ if (url[0] == '/') {
+ /* Relative redirect; reuse original host name and port */
+ path_search_start = url;
+ git__free(data->host);
+ data->host = original_host;
+ original_host = NULL;
+ }
+
+ if (!error) {
+ const char *path = strchr(path_search_start, '/');
+ size_t pathlen = strlen(path);
+ size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
+
+ if (suffixlen &&
+ !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
+ git__free(data->path);
+ data->path = git__strndup(path, pathlen - suffixlen);
+ } else {
+ git__free(data->path);
+ data->path = git__strdup(path);
+ }
+
+ /* Check for errors in the resulting data */
+ if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
+ giterr_set(GITERR_NET, "Cross host redirect not allowed");
+ error = -1;
+ }
+ }
+
+cleanup:
+ if (original_host) git__free(original_host);
+ return error;
+}
+
+void gitno_connection_data_free_ptrs(gitno_connection_data *d)
+{
+ git__free(d->host); d->host = NULL;
+ git__free(d->port); d->port = NULL;
+ git__free(d->path); d->path = NULL;
+ git__free(d->user); d->user = NULL;
+ git__free(d->pass); d->pass = NULL;
+}
+
+#define hex2c(c) ((c | 32) % 39 - 9)
+static char* unescape(char *str)
+{
+ int x, y;
+ int len = (int)strlen(str);
+
+ for (x=y=0; str[y]; ++x, ++y) {
+ if ((str[x] = str[y]) == '%') {
+ if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) {
+ str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]);
+ y += 2;
+ }
+ }
+ }
+ str[x] = '\0';
+ return str;
+}
+
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **path,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port)
+{
+ struct http_parser_url u = {0};
+ const char *_host, *_port, *_path, *_userinfo;
+
+ if (http_parser_parse_url(url, strlen(url), false, &u)) {
+ giterr_set(GITERR_NET, "Malformed URL '%s'", url);
+ return GIT_EINVALIDSPEC;
+ }
+
+ _host = url+u.field_data[UF_HOST].off;
+ _port = url+u.field_data[UF_PORT].off;
+ _path = url+u.field_data[UF_PATH].off;
+ _userinfo = url+u.field_data[UF_USERINFO].off;
+
+ if (u.field_set & (1 << UF_HOST)) {
+ *host = git__substrdup(_host, u.field_data[UF_HOST].len);
+ GITERR_CHECK_ALLOC(*host);
+ }
+
+ if (u.field_set & (1 << UF_PORT))
+ *port = git__substrdup(_port, u.field_data[UF_PORT].len);
+ else
+ *port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(*port);
+
+ if (path) {
+ if (u.field_set & (1 << UF_PATH)) {
+ *path = git__substrdup(_path, u.field_data[UF_PATH].len);
+ GITERR_CHECK_ALLOC(*path);
+ } else {
+ git__free(*port);
+ *port = NULL;
+ git__free(*host);
+ *host = NULL;
+ giterr_set(GITERR_NET, "invalid url, missing path");
+ return GIT_EINVALIDSPEC;
+ }
+ }
+
+ if (u.field_set & (1 << UF_USERINFO)) {
+ const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
+ if (colon) {
+ *username = unescape(git__substrdup(_userinfo, colon - _userinfo));
+ *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo)));
+ GITERR_CHECK_ALLOC(*password);
+ } else {
+ *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
+ }
+ GITERR_CHECK_ALLOC(*username);
+
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_netops_h__
+#define INCLUDE_netops_h__
+
+#include "posix.h"
+#include "common.h"
+#include "stream.h"
+
+#ifdef GIT_OPENSSL
+# include <openssl/ssl.h>
+#endif
+
+typedef struct gitno_ssl {
+#ifdef GIT_OPENSSL
+ SSL *ssl;
+#else
+ size_t dummy;
+#endif
+} gitno_ssl;
+
+/* Represents a socket that may or may not be using SSL */
+typedef struct gitno_socket {
+ GIT_SOCKET socket;
+ gitno_ssl ssl;
+} gitno_socket;
+
+typedef struct gitno_buffer {
+ char *data;
+ size_t len;
+ size_t offset;
+ int (*recv)(struct gitno_buffer *buffer);
+ void *cb_data;
+} gitno_buffer;
+
+/* Flags to gitno_connect */
+enum {
+ /* Attempt to create an SSL connection. */
+ GITNO_CONNECT_SSL = 1,
+};
+
+/**
+ * Check if the name in a cert matches the wanted hostname
+ *
+ * Check if a pattern from a certificate matches the hostname we
+ * wanted to connect to according to RFC2818 rules (which specifies
+ * HTTP over TLS). Mainly, an asterisk matches anything, but is
+ * limited to a single url component.
+ *
+ * Note that this does not set an error message. It expects the user
+ * to provide the message for the user.
+ */
+int gitno__match_host(const char *pattern, const char *host);
+
+void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len);
+void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
+int gitno_recv(gitno_buffer *buf);
+
+void gitno_consume(gitno_buffer *buf, const char *ptr);
+void gitno_consume_n(gitno_buffer *buf, size_t cons);
+
+typedef struct gitno_connection_data {
+ char *host;
+ char *port;
+ char *path;
+ char *user;
+ char *pass;
+ bool use_ssl;
+} gitno_connection_data;
+
+/*
+ * This replaces all the pointers in `data` with freshly-allocated strings,
+ * that the caller is responsible for freeing.
+ * `gitno_connection_data_free_ptrs` is good for this.
+ */
+
+int gitno_connection_data_from_url(
+ gitno_connection_data *data,
+ const char *url,
+ const char *service_suffix);
+
+/* This frees all the pointers IN the struct, but not the struct itself. */
+void gitno_connection_data_free_ptrs(gitno_connection_data *data);
+
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **path,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "notes.h"
+
+#include "git2.h"
+#include "refs.h"
+#include "config.h"
+#include "iterator.h"
+#include "signature.h"
+
+static int note_error_notfound(void)
+{
+ giterr_set(GITERR_INVALID, "Note could not be found");
+ return GIT_ENOTFOUND;
+}
+
+static int find_subtree_in_current_level(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ const char *annotated_object_sha,
+ int fanout)
+{
+ size_t i;
+ const git_tree_entry *entry;
+
+ *out = NULL;
+
+ if (parent == NULL)
+ return note_error_notfound();
+
+ for (i = 0; i < git_tree_entrycount(parent); i++) {
+ entry = git_tree_entry_byindex(parent, i);
+
+ if (!git__ishex(git_tree_entry_name(entry)))
+ continue;
+
+ if (S_ISDIR(git_tree_entry_filemode(entry))
+ && strlen(git_tree_entry_name(entry)) == 2
+ && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2))
+ return git_tree_lookup(out, repo, git_tree_entry_id(entry));
+
+ /* Not a DIR, so do we have an already existing blob? */
+ if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout))
+ return GIT_EEXISTS;
+ }
+
+ return note_error_notfound();
+}
+
+static int find_subtree_r(git_tree **out, git_tree *root,
+ git_repository *repo, const char *target, int *fanout)
+{
+ int error;
+ git_tree *subtree = NULL;
+
+ *out = NULL;
+
+ error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
+ if (error == GIT_EEXISTS)
+ return git_tree_lookup(out, repo, git_tree_id(root));
+
+ if (error < 0)
+ return error;
+
+ *fanout += 2;
+ error = find_subtree_r(out, subtree, repo, target, fanout);
+ git_tree_free(subtree);
+
+ return error;
+}
+
+static int find_blob(git_oid *blob, git_tree *tree, const char *target)
+{
+ size_t i;
+ const git_tree_entry *entry;
+
+ for (i=0; i<git_tree_entrycount(tree); i++) {
+ entry = git_tree_entry_byindex(tree, i);
+
+ if (!strcmp(git_tree_entry_name(entry), target)) {
+ /* found matching note object - return */
+
+ git_oid_cpy(blob, git_tree_entry_id(entry));
+ return 0;
+ }
+ }
+
+ return note_error_notfound();
+}
+
+static int tree_write(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *source_tree,
+ const git_oid *object_oid,
+ const char *treeentry_name,
+ unsigned int attributes)
+{
+ int error;
+ git_treebuilder *tb = NULL;
+ const git_tree_entry *entry;
+ git_oid tree_oid;
+
+ if ((error = git_treebuilder_new(&tb, repo, source_tree)) < 0)
+ goto cleanup;
+
+ if (object_oid) {
+ if ((error = git_treebuilder_insert(
+ &entry, tb, treeentry_name, object_oid, attributes)) < 0)
+ goto cleanup;
+ } else {
+ if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_treebuilder_write(&tree_oid, tb)) < 0)
+ goto cleanup;
+
+ error = git_tree_lookup(out, repo, &tree_oid);
+
+cleanup:
+ git_treebuilder_free(tb);
+ return error;
+}
+
+static int manipulate_note_in_tree_r(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int (*note_exists_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error),
+ int (*note_notfound_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error))
+{
+ int error;
+ git_tree *subtree = NULL, *new = NULL;
+ char subtree_name[3];
+
+ error = find_subtree_in_current_level(
+ &subtree, repo, parent, annotated_object_sha, fanout);
+
+ if (error == GIT_EEXISTS) {
+ error = note_exists_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
+
+ if (error == GIT_ENOTFOUND) {
+ error = note_notfound_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ /* An existing fanout has been found, let's dig deeper */
+ error = manipulate_note_in_tree_r(
+ &new, repo, subtree, note_oid, annotated_object_sha,
+ fanout + 2, note_exists_cb, note_notfound_cb);
+
+ if (error < 0)
+ goto cleanup;
+
+ strncpy(subtree_name, annotated_object_sha + fanout, 2);
+ subtree_name[2] = '\0';
+
+ error = tree_write(out, repo, parent, git_tree_id(new),
+ subtree_name, GIT_FILEMODE_TREE);
+
+
+cleanup:
+ git_tree_free(new);
+ git_tree_free(subtree);
+ return error;
+}
+
+static int remove_note_in_tree_eexists_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(current_error);
+
+ return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
+}
+
+static int remove_note_in_tree_enotfound_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
+ return current_error;
+}
+
+static int insert_note_in_tree_eexists_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
+ return current_error;
+}
+
+static int insert_note_in_tree_enotfound_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(current_error);
+
+ /* No existing fanout at this level, insert in place */
+ return tree_write(
+ out,
+ repo,
+ parent,
+ note_oid,
+ annotated_object_sha + fanout,
+ GIT_FILEMODE_BLOB);
+}
+
+static int note_write(git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const char *note,
+ git_tree *commit_tree,
+ const char *target,
+ git_commit **parents,
+ int allow_note_overwrite)
+{
+ int error;
+ git_oid oid;
+ git_tree *tree = NULL;
+
+ // TODO: should we apply filters?
+ /* create note object */
+ if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)
+ goto cleanup;
+
+ if ((error = manipulate_note_in_tree_r(
+ &tree, repo, commit_tree, &oid, target, 0,
+ allow_note_overwrite ? insert_note_in_tree_enotfound_cb : insert_note_in_tree_eexists_cb,
+ insert_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
+
+ if (out)
+ git_oid_cpy(out, &oid);
+
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_ADD,
+ tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
+
+cleanup:
+ git_tree_free(tree);
+ return error;
+}
+
+static int note_new(
+ git_note **out,
+ git_oid *note_oid,
+ git_commit *commit,
+ git_blob *blob)
+{
+ git_note *note = NULL;
+
+ note = git__malloc(sizeof(git_note));
+ GITERR_CHECK_ALLOC(note);
+
+ git_oid_cpy(¬e->id, note_oid);
+
+ if (git_signature_dup(¬e->author, git_commit_author(commit)) < 0 ||
+ git_signature_dup(¬e->committer, git_commit_committer(commit)) < 0)
+ return -1;
+
+ note->message = git__strndup(git_blob_rawcontent(blob), git_blob_rawsize(blob));
+ GITERR_CHECK_ALLOC(note->message);
+
+ *out = note;
+ return 0;
+}
+
+static int note_lookup(
+ git_note **out,
+ git_repository *repo,
+ git_commit *commit,
+ git_tree *tree,
+ const char *target)
+{
+ int error, fanout = 0;
+ git_oid oid;
+ git_blob *blob = NULL;
+ git_note *note = NULL;
+ git_tree *subtree = NULL;
+
+ if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)
+ goto cleanup;
+
+ if ((error = find_blob(&oid, subtree, target + fanout)) < 0)
+ goto cleanup;
+
+ if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)
+ goto cleanup;
+
+ if ((error = note_new(¬e, &oid, commit, blob)) < 0)
+ goto cleanup;
+
+ *out = note;
+
+cleanup:
+ git_tree_free(subtree);
+ git_blob_free(blob);
+ return error;
+}
+
+static int note_remove(git_repository *repo,
+ const git_signature *author, const git_signature *committer,
+ const char *notes_ref, git_tree *tree,
+ const char *target, git_commit **parents)
+{
+ int error;
+ git_tree *tree_after_removal = NULL;
+ git_oid oid;
+
+ if ((error = manipulate_note_in_tree_r(
+ &tree_after_removal, repo, tree, NULL, target, 0,
+ remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
+
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_RM,
+ tree_after_removal,
+ *parents == NULL ? 0 : 1,
+ (const git_commit **) parents);
+
+cleanup:
+ git_tree_free(tree_after_removal);
+ return error;
+}
+
+static int note_get_default_ref(char **out, git_repository *repo)
+{
+ git_config *cfg;
+ int ret = git_repository_config__weakptr(&cfg, repo);
+
+ *out = (ret != 0) ? NULL : git_config__get_string_force(
+ cfg, "core.notesref", GIT_NOTES_DEFAULT_REF);
+
+ return ret;
+}
+
+static int normalize_namespace(char **out, git_repository *repo, const char *notes_ref)
+{
+ if (notes_ref) {
+ *out = git__strdup(notes_ref);
+ GITERR_CHECK_ALLOC(*out);
+ return 0;
+ }
+
+ return note_get_default_ref(out, repo);
+}
+
+static int retrieve_note_tree_and_commit(
+ git_tree **tree_out,
+ git_commit **commit_out,
+ char **notes_ref_out,
+ git_repository *repo,
+ const char *notes_ref)
+{
+ int error;
+ git_oid oid;
+
+ if ((error = normalize_namespace(notes_ref_out, repo, notes_ref)) < 0)
+ return error;
+
+ if ((error = git_reference_name_to_id(&oid, repo, *notes_ref_out)) < 0)
+ return error;
+
+ if (git_commit_lookup(commit_out, repo, &oid) < 0)
+ return error;
+
+ if ((error = git_commit_tree(tree_out, *commit_out)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_note_read(git_note **out, git_repository *repo,
+ const char *notes_ref_in, const git_oid *oid)
+{
+ int error;
+ char *target = NULL, *notes_ref = NULL;
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, ¬es_ref, repo, notes_ref_in)))
+ error = note_lookup(out, repo, commit, tree, target);
+
+ git__free(notes_ref);
+ git__free(target);
+ git_tree_free(tree);
+ git_commit_free(commit);
+ return error;
+}
+
+int git_note_create(
+ git_oid *out,
+ git_repository *repo,
+ const char *notes_ref_in,
+ const git_signature *author,
+ const git_signature *committer,
+ const git_oid *oid,
+ const char *note,
+ int allow_note_overwrite)
+{
+ int error;
+ char *target = NULL, *notes_ref = NULL;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ error = retrieve_note_tree_and_commit(&tree, &commit, ¬es_ref, repo, notes_ref_in);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ error = note_write(out, repo, author, committer, notes_ref,
+ note, tree, target, &commit, allow_note_overwrite);
+
+cleanup:
+ git__free(notes_ref);
+ git__free(target);
+ git_commit_free(commit);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_note_remove(git_repository *repo, const char *notes_ref_in,
+ const git_signature *author, const git_signature *committer,
+ const git_oid *oid)
+{
+ int error;
+ char *target = NULL, *notes_ref;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, ¬es_ref, repo, notes_ref_in)))
+ error = note_remove(
+ repo, author, committer, notes_ref, tree, target, &commit);
+
+ git__free(notes_ref);
+ git__free(target);
+ git_commit_free(commit);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_note_default_ref(git_buf *out, git_repository *repo)
+{
+ char *default_ref;
+ int error;
+
+ assert(out && repo);
+
+ git_buf_sanitize(out);
+
+ if ((error = note_get_default_ref(&default_ref, repo)) < 0)
+ return error;
+
+ git_buf_attach(out, default_ref, strlen(default_ref));
+ return 0;
+}
+
+const git_signature *git_note_committer(const git_note *note)
+{
+ assert(note);
+ return note->committer;
+}
+
+const git_signature *git_note_author(const git_note *note)
+{
+ assert(note);
+ return note->author;
+}
+
+const char * git_note_message(const git_note *note)
+{
+ assert(note);
+ return note->message;
+}
+
+const git_oid * git_note_id(const git_note *note)
+{
+ assert(note);
+ return ¬e->id;
+}
+
+void git_note_free(git_note *note)
+{
+ if (note == NULL)
+ return;
+
+ git_signature_free(note->committer);
+ git_signature_free(note->author);
+ git__free(note->message);
+ git__free(note);
+}
+
+static int process_entry_path(
+ const char* entry_path,
+ git_oid *annotated_object_id)
+{
+ int error = 0;
+ size_t i = 0, j = 0, len;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_buf_puts(&buf, entry_path)) < 0)
+ goto cleanup;
+
+ len = git_buf_len(&buf);
+
+ while (i < len) {
+ if (buf.ptr[i] == '/') {
+ i++;
+ continue;
+ }
+
+ if (git__fromhex(buf.ptr[i]) < 0) {
+ /* This is not a note entry */
+ goto cleanup;
+ }
+
+ if (i != j)
+ buf.ptr[j] = buf.ptr[i];
+
+ i++;
+ j++;
+ }
+
+ buf.ptr[j] = '\0';
+ buf.size = j;
+
+ if (j != GIT_OID_HEXSZ) {
+ /* This is not a note entry */
+ goto cleanup;
+ }
+
+ error = git_oid_fromstr(annotated_object_id, buf.ptr);
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_note_foreach(
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload)
+{
+ int error;
+ git_note_iterator *iter = NULL;
+ git_oid note_id, annotated_id;
+
+ if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)
+ return error;
+
+ while (!(error = git_note_next(¬e_id, &annotated_id, iter))) {
+ if ((error = note_cb(¬e_id, &annotated_id, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_note_iterator_free(iter);
+ return error;
+}
+
+
+void git_note_iterator_free(git_note_iterator *it)
+{
+ if (it == NULL)
+ return;
+
+ git_iterator_free(it);
+}
+
+
+int git_note_iterator_new(
+ git_note_iterator **it,
+ git_repository *repo,
+ const char *notes_ref_in)
+{
+ int error;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+ char *notes_ref;
+
+ error = retrieve_note_tree_and_commit(&tree, &commit, ¬es_ref, repo, notes_ref_in);
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_for_tree(it, tree, NULL)) < 0)
+ git_iterator_free(*it);
+
+cleanup:
+ git__free(notes_ref);
+ git_tree_free(tree);
+ git_commit_free(commit);
+
+ return error;
+}
+
+int git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it)
+{
+ int error;
+ const git_index_entry *item;
+
+ if ((error = git_iterator_current(&item, it)) < 0)
+ return error;
+
+ git_oid_cpy(note_id, &item->id);
+
+ if (!(error = process_entry_path(item->path, annotated_id)))
+ git_iterator_advance(NULL, it);
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_note_h__
+#define INCLUDE_note_h__
+
+#include "common.h"
+
+#include "git2/oid.h"
+#include "git2/types.h"
+
+#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+
+#define GIT_NOTES_DEFAULT_MSG_ADD \
+ "Notes added by 'git_note_create' from libgit2"
+
+#define GIT_NOTES_DEFAULT_MSG_RM \
+ "Notes removed by 'git_note_remove' from libgit2"
+
+struct git_note {
+ git_oid id;
+
+ git_signature *author;
+ git_signature *committer;
+
+ char *message;
+};
+
+#endif /* INCLUDE_notes_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/object.h"
+
+#include "common.h"
+#include "repository.h"
+
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "oid.h"
+#include "tag.h"
+
+bool git_object__strict_input_validation = true;
+
+typedef struct {
+ const char *str; /* type name string */
+ size_t size; /* size in bytes of the object structure */
+
+ int (*parse)(void *self, git_odb_object *obj);
+ void (*free)(void *self);
+} git_object_def;
+
+static git_object_def git_objects_table[] = {
+ /* 0 = GIT_OBJ__EXT1 */
+ { "", 0, NULL, NULL },
+
+ /* 1 = GIT_OBJ_COMMIT */
+ { "commit", sizeof(git_commit), git_commit__parse, git_commit__free },
+
+ /* 2 = GIT_OBJ_TREE */
+ { "tree", sizeof(git_tree), git_tree__parse, git_tree__free },
+
+ /* 3 = GIT_OBJ_BLOB */
+ { "blob", sizeof(git_blob), git_blob__parse, git_blob__free },
+
+ /* 4 = GIT_OBJ_TAG */
+ { "tag", sizeof(git_tag), git_tag__parse, git_tag__free },
+
+ /* 5 = GIT_OBJ__EXT2 */
+ { "", 0, NULL, NULL },
+ /* 6 = GIT_OBJ_OFS_DELTA */
+ { "OFS_DELTA", 0, NULL, NULL },
+ /* 7 = GIT_OBJ_REF_DELTA */
+ { "REF_DELTA", 0, NULL, NULL },
+};
+
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type)
+{
+ int error;
+ size_t object_size;
+ git_object_def *def;
+ git_object *object = NULL;
+
+ assert(object_out);
+ *object_out = NULL;
+
+ /* Validate type match */
+ if (type != GIT_OBJ_ANY && type != odb_obj->cached.type) {
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in the ODB");
+ return GIT_ENOTFOUND;
+ }
+
+ if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
+ giterr_set(GITERR_INVALID, "The requested type is invalid");
+ return GIT_ENOTFOUND;
+ }
+
+ /* Allocate and initialize base object */
+ object = git__calloc(1, object_size);
+ GITERR_CHECK_ALLOC(object);
+
+ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
+ object->cached.type = odb_obj->cached.type;
+ object->cached.size = odb_obj->cached.size;
+ object->repo = repo;
+
+ /* Parse raw object data */
+ def = &git_objects_table[odb_obj->cached.type];
+ assert(def->free && def->parse);
+
+ if ((error = def->parse(object, odb_obj)) < 0)
+ def->free(object);
+ else
+ *object_out = git_cache_store_parsed(&repo->objects, object);
+
+ return error;
+}
+
+void git_object__free(void *obj)
+{
+ git_otype type = ((git_object *)obj)->cached.type;
+
+ if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) ||
+ !git_objects_table[type].free)
+ git__free(obj);
+ else
+ git_objects_table[type].free(obj);
+}
+
+int git_object_lookup_prefix(
+ git_object **object_out,
+ git_repository *repo,
+ const git_oid *id,
+ size_t len,
+ git_otype type)
+{
+ git_object *object = NULL;
+ git_odb *odb = NULL;
+ git_odb_object *odb_obj = NULL;
+ int error = 0;
+
+ assert(repo && object_out && id);
+
+ if (len < GIT_OID_MINPREFIXLEN) {
+ giterr_set(GITERR_OBJECT, "Ambiguous lookup - OID prefix is too short");
+ return GIT_EAMBIGUOUS;
+ }
+
+ error = git_repository_odb__weakptr(&odb, repo);
+ if (error < 0)
+ return error;
+
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ if (len == GIT_OID_HEXSZ) {
+ git_cached_obj *cached = NULL;
+
+ /* We want to match the full id : we can first look up in the cache,
+ * since there is no need to check for non ambiguousity
+ */
+ cached = git_cache_get_any(&repo->objects, id);
+ if (cached != NULL) {
+ if (cached->flags == GIT_CACHE_STORE_PARSED) {
+ object = (git_object *)cached;
+
+ if (type != GIT_OBJ_ANY && type != object->cached.type) {
+ git_object_free(object);
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in ODB");
+ return GIT_ENOTFOUND;
+ }
+
+ *object_out = object;
+ return 0;
+ } else if (cached->flags == GIT_CACHE_STORE_RAW) {
+ odb_obj = (git_odb_object *)cached;
+ } else {
+ assert(!"Wrong caching type in the global object cache");
+ }
+ } else {
+ /* Object was not found in the cache, let's explore the backends.
+ * We could just use git_odb_read_unique_short_oid,
+ * it is the same cost for packed and loose object backends,
+ * but it may be much more costly for sqlite and hiredis.
+ */
+ error = git_odb_read(&odb_obj, odb, id);
+ }
+ } else {
+ git_oid short_oid = {{ 0 }};
+
+ git_oid__cpy_prefix(&short_oid, id, len);
+
+ /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
+ * 2 options :
+ * - We always search in the cache first. If we find that short oid is
+ * ambiguous, we can stop. But in all the other cases, we must then
+ * explore all the backends (to find an object if there was match,
+ * or to check that oid is not ambiguous if we have found 1 match in
+ * the cache)
+ * - We never explore the cache, go right to exploring the backends
+ * We chose the latter : we explore directly the backends.
+ */
+ error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len);
+ }
+
+ if (error < 0)
+ return error;
+
+ error = git_object__from_odb_object(object_out, repo, odb_obj, type);
+
+ git_odb_object_free(odb_obj);
+
+ return error;
+}
+
+int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
+ return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
+}
+
+void git_object_free(git_object *object)
+{
+ if (object == NULL)
+ return;
+
+ git_cached_obj_decref(object);
+}
+
+const git_oid *git_object_id(const git_object *obj)
+{
+ assert(obj);
+ return &obj->cached.oid;
+}
+
+git_otype git_object_type(const git_object *obj)
+{
+ assert(obj);
+ return obj->cached.type;
+}
+
+git_repository *git_object_owner(const git_object *obj)
+{
+ assert(obj);
+ return obj->repo;
+}
+
+const char *git_object_type2string(git_otype type)
+{
+ if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
+ return "";
+
+ return git_objects_table[type].str;
+}
+
+git_otype git_object_string2type(const char *str)
+{
+ size_t i;
+
+ if (!str || !*str)
+ return GIT_OBJ_BAD;
+
+ for (i = 0; i < ARRAY_SIZE(git_objects_table); i++)
+ if (!strcmp(str, git_objects_table[i].str))
+ return (git_otype)i;
+
+ return GIT_OBJ_BAD;
+}
+
+int git_object_typeisloose(git_otype type)
+{
+ if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
+ return 0;
+
+ return (git_objects_table[type].size > 0) ? 1 : 0;
+}
+
+size_t git_object__size(git_otype type)
+{
+ if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
+ return 0;
+
+ return git_objects_table[type].size;
+}
+
+static int dereference_object(git_object **dereferenced, git_object *obj)
+{
+ git_otype type = git_object_type(obj);
+
+ switch (type) {
+ case GIT_OBJ_COMMIT:
+ return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);
+
+ case GIT_OBJ_TAG:
+ return git_tag_target(dereferenced, (git_tag*)obj);
+
+ case GIT_OBJ_BLOB:
+ case GIT_OBJ_TREE:
+ return GIT_EPEEL;
+
+ default:
+ return GIT_EINVALIDSPEC;
+ }
+}
+
+static int peel_error(int error, const git_oid *oid, git_otype type)
+{
+ const char *type_name;
+ char hex_oid[GIT_OID_HEXSZ + 1];
+
+ type_name = git_object_type2string(type);
+
+ git_oid_fmt(hex_oid, oid);
+ hex_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "
+ "successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);
+
+ return error;
+}
+
+static int check_type_combination(git_otype type, git_otype target)
+{
+ if (type == target)
+ return 0;
+
+ switch (type) {
+ case GIT_OBJ_BLOB:
+ case GIT_OBJ_TREE:
+ /* a blob or tree can never be peeled to anything but themselves */
+ return GIT_EINVALIDSPEC;
+ break;
+ case GIT_OBJ_COMMIT:
+ /* a commit can only be peeled to a tree */
+ if (target != GIT_OBJ_TREE && target != GIT_OBJ_ANY)
+ return GIT_EINVALIDSPEC;
+ break;
+ case GIT_OBJ_TAG:
+ /* a tag may point to anything, so we let anything through */
+ break;
+ default:
+ return GIT_EINVALIDSPEC;
+ }
+
+ return 0;
+}
+
+int git_object_peel(
+ git_object **peeled,
+ const git_object *object,
+ git_otype target_type)
+{
+ git_object *source, *deref = NULL;
+ int error;
+
+ assert(object && peeled);
+
+ assert(target_type == GIT_OBJ_TAG ||
+ target_type == GIT_OBJ_COMMIT ||
+ target_type == GIT_OBJ_TREE ||
+ target_type == GIT_OBJ_BLOB ||
+ target_type == GIT_OBJ_ANY);
+
+ if ((error = check_type_combination(git_object_type(object), target_type)) < 0)
+ return peel_error(error, git_object_id(object), target_type);
+
+ if (git_object_type(object) == target_type)
+ return git_object_dup(peeled, (git_object *)object);
+
+ source = (git_object *)object;
+
+ while (!(error = dereference_object(&deref, source))) {
+
+ if (source != object)
+ git_object_free(source);
+
+ if (git_object_type(deref) == target_type) {
+ *peeled = deref;
+ return 0;
+ }
+
+ if (target_type == GIT_OBJ_ANY &&
+ git_object_type(deref) != git_object_type(object))
+ {
+ *peeled = deref;
+ return 0;
+ }
+
+ source = deref;
+ deref = NULL;
+ }
+
+ if (source != object)
+ git_object_free(source);
+
+ git_object_free(deref);
+
+ if (error)
+ error = peel_error(error, git_object_id(object), target_type);
+
+ return error;
+}
+
+int git_object_dup(git_object **dest, git_object *source)
+{
+ git_cached_obj_incref(source);
+ *dest = source;
+ return 0;
+}
+
+int git_object_lookup_bypath(
+ git_object **out,
+ const git_object *treeish,
+ const char *path,
+ git_otype type)
+{
+ int error = -1;
+ git_tree *tree = NULL;
+ git_tree_entry *entry = NULL;
+
+ assert(out && treeish && path);
+
+ if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0 ||
+ (error = git_tree_entry_bypath(&entry, tree, path)) < 0)
+ {
+ goto cleanup;
+ }
+
+ if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type)
+ {
+ giterr_set(GITERR_OBJECT,
+ "object at path '%s' is not of the asked-for type %d",
+ path, type);
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ error = git_tree_entry_to_object(out, git_object_owner(treeish), entry);
+
+cleanup:
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_object_short_id(git_buf *out, const git_object *obj)
+{
+ git_repository *repo;
+ int len = GIT_ABBREV_DEFAULT, error;
+ git_oid id = {{0}};
+ git_odb *odb;
+
+ assert(out && obj);
+
+ git_buf_sanitize(out);
+ repo = git_object_owner(obj);
+
+ if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0)
+ return error;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ while (len < GIT_OID_HEXSZ) {
+ /* set up short oid */
+ memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2);
+ if (len & 1)
+ id.id[len / 2] &= 0xf0;
+
+ error = git_odb_exists_prefix(NULL, odb, &id, len);
+ if (error != GIT_EAMBIGUOUS)
+ break;
+
+ giterr_clear();
+ len++;
+ }
+
+ if (!error && !(error = git_buf_grow(out, len + 1))) {
+ git_oid_tostr(out->ptr, len + 1, &id);
+ out->size = len;
+ }
+
+ git_odb_free(odb);
+
+ return error;
+}
+
+bool git_object__is_valid(
+ git_repository *repo, const git_oid *id, git_otype expected_type)
+{
+ git_odb *odb;
+ git_otype actual_type;
+ size_t len;
+ int error;
+
+ if (!git_object__strict_input_validation)
+ return true;
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0)
+ return false;
+
+ if (expected_type != GIT_OBJ_ANY && expected_type != actual_type) {
+ giterr_set(GITERR_INVALID,
+ "the requested type does not match the type in the ODB");
+ return false;
+ }
+
+ return true;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_object_h__
+#define INCLUDE_object_h__
+
+#include "repository.h"
+
+extern bool git_object__strict_input_validation;
+
+/** Base git object for inheritance */
+struct git_object {
+ git_cached_obj cached;
+ git_repository *repo;
+};
+
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_object__free(void *object);
+
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type);
+
+int git_object__resolve_to_type(git_object **obj, git_otype type);
+
+int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
+
+void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
+
+bool git_object__is_valid(
+ git_repository *repo, const git_oid *id, git_otype expected_type);
+
+GIT_INLINE(git_otype) git_object__type_from_filemode(git_filemode_t mode)
+{
+ switch (mode) {
+ case GIT_FILEMODE_TREE:
+ return GIT_OBJ_TREE;
+ case GIT_FILEMODE_COMMIT:
+ return GIT_OBJ_COMMIT;
+ case GIT_FILEMODE_BLOB:
+ case GIT_FILEMODE_BLOB_EXECUTABLE:
+ case GIT_FILEMODE_LINK:
+ return GIT_OBJ_BLOB;
+ default:
+ return GIT_OBJ_BAD;
+ }
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/object.h"
+
+#include "common.h"
+#include "repository.h"
+
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+
+/**
+ * Commit
+ */
+int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_COMMIT);
+}
+
+int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_COMMIT);
+}
+
+void git_commit_free(git_commit *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_commit_id(const git_commit *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_commit_owner(const git_commit *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+int git_commit_dup(git_commit **out, git_commit *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
+
+/**
+ * Tree
+ */
+int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TREE);
+}
+
+int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TREE);
+}
+
+void git_tree_free(git_tree *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tree_id(const git_tree *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tree_owner(const git_tree *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+int git_tree_dup(git_tree **out, git_tree *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
+
+/**
+ * Tag
+ */
+int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TAG);
+}
+
+int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TAG);
+}
+
+void git_tag_free(git_tag *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tag_id(const git_tag *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tag_owner(const git_tag *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+int git_tag_dup(git_tag **out, git_tag *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
+
+/**
+ * Blob
+ */
+int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_BLOB);
+}
+
+int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_BLOB);
+}
+
+void git_blob_free(git_blob *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_blob_id(const git_blob *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_blob_owner(const git_blob *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+int git_blob_dup(git_blob **out, git_blob *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include <zlib.h>
+#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
+#include "fileops.h"
+#include "hash.h"
+#include "odb.h"
+#include "delta.h"
+#include "filter.h"
+#include "repository.h"
+
+#include "git2/odb_backend.h"
+#include "git2/oid.h"
+#include "git2/oidarray.h"
+
+#define GIT_ALTERNATES_FILE "info/alternates"
+
+/*
+ * We work under the assumption that most objects for long-running
+ * operations will be packed
+ */
+#define GIT_LOOSE_PRIORITY 1
+#define GIT_PACKED_PRIORITY 2
+
+#define GIT_ALTERNATES_MAX_DEPTH 5
+
+typedef struct
+{
+ git_odb_backend *backend;
+ int priority;
+ bool is_alternate;
+ ino_t disk_inode;
+} backend_internal;
+
+static git_cache *odb_cache(git_odb *odb)
+{
+ if (odb->rc.owner != NULL) {
+ git_repository *owner = odb->rc.owner;
+ return &owner->objects;
+ }
+
+ return &odb->own_cache;
+}
+
+static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id);
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+
+static git_otype odb_hardcoded_type(const git_oid *id)
+{
+ static git_oid empty_tree = {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60,
+ 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }};
+
+ if (!git_oid_cmp(id, &empty_tree))
+ return GIT_OBJ_TREE;
+
+ return GIT_OBJ_BAD;
+}
+
+static int odb_read_hardcoded(git_rawobj *raw, const git_oid *id)
+{
+ git_otype type = odb_hardcoded_type(id);
+ if (type == GIT_OBJ_BAD)
+ return -1;
+
+ raw->type = type;
+ raw->len = 0;
+ raw->data = git__calloc(1, sizeof(uint8_t));
+ return 0;
+}
+
+int git_odb__format_object_header(char *hdr, size_t n, git_off_t obj_len, git_otype obj_type)
+{
+ const char *type_str = git_object_type2string(obj_type);
+ int len = p_snprintf(hdr, n, "%s %lld", type_str, (long long)obj_len);
+ assert(len > 0 && len <= (int)n);
+ return len+1;
+}
+
+int git_odb__hashobj(git_oid *id, git_rawobj *obj)
+{
+ git_buf_vec vec[2];
+ char header[64];
+ int hdrlen;
+
+ assert(id && obj);
+
+ if (!git_object_typeisloose(obj->type))
+ return -1;
+
+ if (!obj->data && obj->len != 0)
+ return -1;
+
+ hdrlen = git_odb__format_object_header(header, sizeof(header), obj->len, obj->type);
+
+ vec[0].data = header;
+ vec[0].len = hdrlen;
+ vec[1].data = obj->data;
+ vec[1].len = obj->len;
+
+ git_hash_vec(id, vec, 2);
+
+ return 0;
+}
+
+
+static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source)
+{
+ git_odb_object *object = git__calloc(1, sizeof(git_odb_object));
+
+ if (object != NULL) {
+ git_oid_cpy(&object->cached.oid, oid);
+ object->cached.type = source->type;
+ object->cached.size = source->len;
+ object->buffer = source->data;
+ }
+
+ return object;
+}
+
+void git_odb_object__free(void *object)
+{
+ if (object != NULL) {
+ git__free(((git_odb_object *)object)->buffer);
+ git__free(object);
+ }
+}
+
+const git_oid *git_odb_object_id(git_odb_object *object)
+{
+ return &object->cached.oid;
+}
+
+const void *git_odb_object_data(git_odb_object *object)
+{
+ return object->buffer;
+}
+
+size_t git_odb_object_size(git_odb_object *object)
+{
+ return object->cached.size;
+}
+
+git_otype git_odb_object_type(git_odb_object *object)
+{
+ return object->cached.type;
+}
+
+int git_odb_object_dup(git_odb_object **dest, git_odb_object *source)
+{
+ git_cached_obj_incref(source);
+ *dest = source;
+ return 0;
+}
+
+void git_odb_object_free(git_odb_object *object)
+{
+ if (object == NULL)
+ return;
+
+ git_cached_obj_decref(object);
+}
+
+int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
+{
+ int hdr_len;
+ char hdr[64], buffer[FILEIO_BUFSIZE];
+ git_hash_ctx ctx;
+ ssize_t read_len = 0;
+ int error = 0;
+
+ if (!git_object_typeisloose(type)) {
+ giterr_set(GITERR_INVALID, "Invalid object type for hash");
+ return -1;
+ }
+
+ if ((error = git_hash_ctx_init(&ctx)) < 0)
+ return -1;
+
+ hdr_len = git_odb__format_object_header(hdr, sizeof(hdr), size, type);
+
+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)
+ goto done;
+
+ while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)
+ goto done;
+
+ size -= read_len;
+ }
+
+ /* If p_read returned an error code, the read obviously failed.
+ * If size is not zero, the file was truncated after we originally
+ * stat'd it, so we consider this a read failure too */
+ if (read_len < 0 || size > 0) {
+ giterr_set(GITERR_OS, "Error reading file for hashing");
+ error = -1;
+
+ goto done;
+ }
+
+ error = git_hash_final(out, &ctx);
+
+done:
+ git_hash_ctx_cleanup(&ctx);
+ return error;
+}
+
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t size, git_otype type, git_filter_list *fl)
+{
+ int error;
+ git_buf raw = GIT_BUF_INIT;
+
+ if (!fl)
+ return git_odb__hashfd(out, fd, size, type);
+
+ /* size of data is used in header, so we have to read the whole file
+ * into memory to apply filters before beginning to calculate the hash
+ */
+
+ if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) {
+ git_buf post = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_data(&post, fl, &raw);
+
+ git_buf_free(&raw);
+
+ if (!error)
+ error = git_odb_hash(out, post.ptr, post.size, type);
+
+ git_buf_free(&post);
+ }
+
+ return error;
+}
+
+int git_odb__hashlink(git_oid *out, const char *path)
+{
+ struct stat st;
+ int size;
+ int result;
+
+ if (git_path_lstat(path, &st) < 0)
+ return -1;
+
+ if (!git__is_int(st.st_size) || (int)st.st_size < 0) {
+ giterr_set(GITERR_FILESYSTEM, "File size overflow for 32-bit systems");
+ return -1;
+ }
+
+ size = (int)st.st_size;
+
+ if (S_ISLNK(st.st_mode)) {
+ char *link_data;
+ int read_len;
+ size_t alloc_size;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_size, size, 1);
+ link_data = git__malloc(alloc_size);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(path, link_data, size);
+ link_data[size] = '\0';
+ if (read_len != size) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path);
+ git__free(link_data);
+ return -1;
+ }
+
+ result = git_odb_hash(out, link_data, size, GIT_OBJ_BLOB);
+ git__free(link_data);
+ } else {
+ int fd = git_futils_open_ro(path);
+ if (fd < 0)
+ return -1;
+ result = git_odb__hashfd(out, fd, size, GIT_OBJ_BLOB);
+ p_close(fd);
+ }
+
+ return result;
+}
+
+int git_odb_hashfile(git_oid *out, const char *path, git_otype type)
+{
+ git_off_t size;
+ int result, fd = git_futils_open_ro(path);
+ if (fd < 0)
+ return fd;
+
+ if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ p_close(fd);
+ return -1;
+ }
+
+ result = git_odb__hashfd(out, fd, (size_t)size, type);
+ p_close(fd);
+ return result;
+}
+
+int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type)
+{
+ git_rawobj raw;
+
+ assert(id);
+
+ raw.data = (void *)data;
+ raw.len = len;
+ raw.type = type;
+
+ return git_odb__hashobj(id, &raw);
+}
+
+/**
+ * FAKE WSTREAM
+ */
+
+typedef struct {
+ git_odb_stream stream;
+ char *buffer;
+ size_t size, written;
+ git_otype type;
+} fake_wstream;
+
+static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid)
+{
+ fake_wstream *stream = (fake_wstream *)_stream;
+ return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type);
+}
+
+static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len)
+{
+ fake_wstream *stream = (fake_wstream *)_stream;
+
+ if (stream->written + len > stream->size)
+ return -1;
+
+ memcpy(stream->buffer + stream->written, data, len);
+ stream->written += len;
+ return 0;
+}
+
+static void fake_wstream__free(git_odb_stream *_stream)
+{
+ fake_wstream *stream = (fake_wstream *)_stream;
+
+ git__free(stream->buffer);
+ git__free(stream);
+}
+
+static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, git_off_t size, git_otype type)
+{
+ fake_wstream *stream;
+
+ if (!git__is_ssizet(size)) {
+ giterr_set(GITERR_ODB, "object size too large to keep in memory");
+ return -1;
+ }
+
+ stream = git__calloc(1, sizeof(fake_wstream));
+ GITERR_CHECK_ALLOC(stream);
+
+ stream->size = size;
+ stream->type = type;
+ stream->buffer = git__malloc(size);
+ if (stream->buffer == NULL) {
+ git__free(stream);
+ return -1;
+ }
+
+ stream->stream.backend = backend;
+ stream->stream.read = NULL; /* read only */
+ stream->stream.write = &fake_wstream__write;
+ stream->stream.finalize_write = &fake_wstream__fwrite;
+ stream->stream.free = &fake_wstream__free;
+ stream->stream.mode = GIT_STREAM_WRONLY;
+
+ *stream_p = (git_odb_stream *)stream;
+ return 0;
+}
+
+/***********************************************************
+ *
+ * OBJECT DATABASE PUBLIC API
+ *
+ * Public calls for the ODB functionality
+ *
+ ***********************************************************/
+
+static int backend_sort_cmp(const void *a, const void *b)
+{
+ const backend_internal *backend_a = (const backend_internal *)(a);
+ const backend_internal *backend_b = (const backend_internal *)(b);
+
+ if (backend_b->priority == backend_a->priority) {
+ if (backend_a->is_alternate)
+ return -1;
+ if (backend_b->is_alternate)
+ return 1;
+ return 0;
+ }
+ return (backend_b->priority - backend_a->priority);
+}
+
+int git_odb_new(git_odb **out)
+{
+ git_odb *db = git__calloc(1, sizeof(*db));
+ GITERR_CHECK_ALLOC(db);
+
+ if (git_cache_init(&db->own_cache) < 0 ||
+ git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) {
+ git__free(db);
+ return -1;
+ }
+
+ *out = db;
+ GIT_REFCOUNT_INC(db);
+ return 0;
+}
+
+static int add_backend_internal(
+ git_odb *odb, git_odb_backend *backend,
+ int priority, bool is_alternate, ino_t disk_inode)
+{
+ backend_internal *internal;
+
+ assert(odb && backend);
+
+ GITERR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend");
+
+ /* Check if the backend is already owned by another ODB */
+ assert(!backend->odb || backend->odb == odb);
+
+ internal = git__malloc(sizeof(backend_internal));
+ GITERR_CHECK_ALLOC(internal);
+
+ internal->backend = backend;
+ internal->priority = priority;
+ internal->is_alternate = is_alternate;
+ internal->disk_inode = disk_inode;
+
+ if (git_vector_insert(&odb->backends, internal) < 0) {
+ git__free(internal);
+ return -1;
+ }
+
+ git_vector_sort(&odb->backends);
+ internal->backend->odb = odb;
+ return 0;
+}
+
+int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority)
+{
+ return add_backend_internal(odb, backend, priority, false, 0);
+}
+
+int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
+{
+ return add_backend_internal(odb, backend, priority, true, 0);
+}
+
+size_t git_odb_num_backends(git_odb *odb)
+{
+ assert(odb);
+ return odb->backends.length;
+}
+
+static int git_odb__error_unsupported_in_backend(const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - unsupported in the loaded odb backends", action);
+ return -1;
+}
+
+
+int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos)
+{
+ backend_internal *internal;
+
+ assert(out && odb);
+ internal = git_vector_get(&odb->backends, pos);
+
+ if (internal && internal->backend) {
+ *out = internal->backend;
+ return 0;
+ }
+
+ giterr_set(GITERR_ODB, "No ODB backend loaded at index %" PRIuZ, pos);
+ return GIT_ENOTFOUND;
+}
+
+static int add_default_backends(
+ git_odb *db, const char *objects_dir,
+ bool as_alternates, int alternate_depth)
+{
+ size_t i;
+ struct stat st;
+ ino_t inode;
+ git_odb_backend *loose, *packed;
+
+ /* TODO: inodes are not really relevant on Win32, so we need to find
+ * a cross-platform workaround for this */
+#ifdef GIT_WIN32
+ GIT_UNUSED(i);
+ GIT_UNUSED(st);
+
+ inode = 0;
+#else
+ if (p_stat(objects_dir, &st) < 0) {
+ if (as_alternates)
+ return 0;
+
+ giterr_set(GITERR_ODB, "Failed to load object database in '%s'", objects_dir);
+ return -1;
+ }
+
+ inode = st.st_ino;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *backend = git_vector_get(&db->backends, i);
+ if (backend->disk_inode == inode)
+ return 0;
+ }
+#endif
+
+ /* add the loose object backend */
+ if (git_odb_backend_loose(&loose, objects_dir, -1, 0, 0, 0) < 0 ||
+ add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates, inode) < 0)
+ return -1;
+
+ /* add the packed file backend */
+ if (git_odb_backend_pack(&packed, objects_dir) < 0 ||
+ add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates, inode) < 0)
+ return -1;
+
+ return load_alternates(db, objects_dir, alternate_depth);
+}
+
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth)
+{
+ git_buf alternates_path = GIT_BUF_INIT;
+ git_buf alternates_buf = GIT_BUF_INIT;
+ char *buffer;
+ const char *alternate;
+ int result = 0;
+
+ /* Git reports an error, we just ignore anything deeper */
+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH)
+ return 0;
+
+ if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
+ return -1;
+
+ if (git_path_exists(alternates_path.ptr) == false) {
+ git_buf_free(&alternates_path);
+ return 0;
+ }
+
+ if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) {
+ git_buf_free(&alternates_path);
+ return -1;
+ }
+
+ buffer = (char *)alternates_buf.ptr;
+
+ /* add each alternate as a new backend; one alternate per line */
+ while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) {
+ if (*alternate == '\0' || *alternate == '#')
+ continue;
+
+ /*
+ * Relative path: build based on the current `objects`
+ * folder. However, relative paths are only allowed in
+ * the current repository.
+ */
+ if (*alternate == '.' && !alternate_depth) {
+ if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0)
+ break;
+ alternate = git_buf_cstr(&alternates_path);
+ }
+
+ if ((result = add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0)
+ break;
+ }
+
+ git_buf_free(&alternates_path);
+ git_buf_free(&alternates_buf);
+
+ return result;
+}
+
+int git_odb_add_disk_alternate(git_odb *odb, const char *path)
+{
+ return add_default_backends(odb, path, true, 0);
+}
+
+int git_odb_open(git_odb **out, const char *objects_dir)
+{
+ git_odb *db;
+
+ assert(out && objects_dir);
+
+ *out = NULL;
+
+ if (git_odb_new(&db) < 0)
+ return -1;
+
+ if (add_default_backends(db, objects_dir, 0, 0) < 0) {
+ git_odb_free(db);
+ return -1;
+ }
+
+ *out = db;
+ return 0;
+}
+
+static void odb_free(git_odb *db)
+{
+ size_t i;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *backend = internal->backend;
+
+ backend->free(backend);
+
+ git__free(internal);
+ }
+
+ git_vector_free(&db->backends);
+ git_cache_free(&db->own_cache);
+
+ git__memzero(db, sizeof(*db));
+ git__free(db);
+}
+
+void git_odb_free(git_odb *db)
+{
+ if (db == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(db, odb_free);
+}
+
+static int odb_exists_1(
+ git_odb *db,
+ const git_oid *id,
+ bool only_refreshed)
+{
+ size_t i;
+ bool found = false;
+
+ for (i = 0; i < db->backends.length && !found; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (b->exists != NULL)
+ found = (bool)b->exists(b, id);
+ }
+
+ return (int)found;
+}
+
+static int odb_freshen_1(
+ git_odb *db,
+ const git_oid *id,
+ bool only_refreshed)
+{
+ size_t i;
+ bool found = false;
+
+ for (i = 0; i < db->backends.length && !found; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (b->freshen != NULL)
+ found = !b->freshen(b, id);
+ else if (b->exists != NULL)
+ found = b->exists(b, id);
+ }
+
+ return (int)found;
+}
+
+static int odb_freshen(git_odb *db, const git_oid *id)
+{
+ assert(db && id);
+
+ if (odb_freshen_1(db, id, false))
+ return 1;
+
+ if (!git_odb_refresh(db))
+ return odb_freshen_1(db, id, true);
+
+ /* Failed to refresh, hence not found */
+ return 0;
+}
+
+int git_odb_exists(git_odb *db, const git_oid *id)
+{
+ git_odb_object *object;
+
+ assert(db && id);
+
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ git_odb_object_free(object);
+ return 1;
+ }
+
+ if (odb_exists_1(db, id, false))
+ return 1;
+
+ if (!git_odb_refresh(db))
+ return odb_exists_1(db, id, true);
+
+ /* Failed to refresh, hence not found */
+ return 0;
+}
+
+static int odb_exists_prefix_1(git_oid *out, git_odb *db,
+ const git_oid *key, size_t len, bool only_refreshed)
+{
+ size_t i;
+ int error = GIT_ENOTFOUND, num_found = 0;
+ git_oid last_found = {{0}}, found;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (!b->exists_prefix)
+ continue;
+
+ error = b->exists_prefix(&found, b, key, len);
+ if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
+ continue;
+ if (error)
+ return error;
+
+ /* make sure found item doesn't introduce ambiguity */
+ if (num_found) {
+ if (git_oid__cmp(&last_found, &found))
+ return git_odb__error_ambiguous("multiple matches for prefix");
+ } else {
+ git_oid_cpy(&last_found, &found);
+ num_found++;
+ }
+ }
+
+ if (!num_found)
+ return GIT_ENOTFOUND;
+
+ if (out)
+ git_oid_cpy(out, &last_found);
+
+ return 0;
+}
+
+int git_odb_exists_prefix(
+ git_oid *out, git_odb *db, const git_oid *short_id, size_t len)
+{
+ int error;
+ git_oid key = {{0}};
+
+ assert(db && short_id);
+
+ if (len < GIT_OID_MINPREFIXLEN)
+ return git_odb__error_ambiguous("prefix length too short");
+
+ if (len >= GIT_OID_HEXSZ) {
+ if (git_odb_exists(db, short_id)) {
+ if (out)
+ git_oid_cpy(out, short_id);
+ return 0;
+ } else {
+ return git_odb__error_notfound(
+ "no match for id prefix", short_id, len);
+ }
+ }
+
+ git_oid__cpy_prefix(&key, short_id, len);
+
+ error = odb_exists_prefix_1(out, db, &key, len, false);
+
+ if (error == GIT_ENOTFOUND && !git_odb_refresh(db))
+ error = odb_exists_prefix_1(out, db, &key, len, true);
+
+ if (error == GIT_ENOTFOUND)
+ return git_odb__error_notfound("no match for id prefix", &key, len);
+
+ return error;
+}
+
+int git_odb_expand_ids(
+ git_odb *db,
+ git_odb_expand_id *ids,
+ size_t count)
+{
+ size_t i;
+
+ assert(db && ids);
+
+ for (i = 0; i < count; i++) {
+ git_odb_expand_id *query = &ids[i];
+ int error = GIT_EAMBIGUOUS;
+
+ if (!query->type)
+ query->type = GIT_OBJ_ANY;
+
+ /* if we have a short OID, expand it first */
+ if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) {
+ git_oid actual_id;
+
+ error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false);
+ if (!error) {
+ git_oid_cpy(&query->id, &actual_id);
+ query->length = GIT_OID_HEXSZ;
+ }
+ }
+
+ /*
+ * now we ought to have a 40-char OID, either because we've expanded it
+ * or because the user passed a full OID. Ensure its type is right.
+ */
+ if (query->length >= GIT_OID_HEXSZ) {
+ git_otype actual_type;
+
+ error = odb_otype_fast(&actual_type, db, &query->id);
+ if (!error) {
+ if (query->type != GIT_OBJ_ANY && query->type != actual_type)
+ error = GIT_ENOTFOUND;
+ else
+ query->type = actual_type;
+ }
+ }
+
+ switch (error) {
+ /* no errors, so we've successfully expanded the OID */
+ case 0:
+ continue;
+
+ /* the object is missing or ambiguous */
+ case GIT_ENOTFOUND:
+ case GIT_EAMBIGUOUS:
+ memset(&query->id, 0, sizeof(git_oid));
+ query->length = 0;
+ query->type = 0;
+ break;
+
+ /* something went very wrong with the ODB; bail hard */
+ default:
+ return error;
+ }
+ }
+
+ giterr_clear();
+ return 0;
+}
+
+int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)
+{
+ int error;
+ git_odb_object *object;
+
+ error = git_odb__read_header_or_object(&object, len_p, type_p, db, id);
+
+ if (object)
+ git_odb_object_free(object);
+
+ return error;
+}
+
+static int odb_read_header_1(
+ size_t *len_p, git_otype *type_p, git_odb *db,
+ const git_oid *id, bool only_refreshed)
+{
+ size_t i;
+ git_otype ht;
+ bool passthrough = false;
+ int error;
+
+ if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJ_BAD) {
+ *type_p = ht;
+ *len_p = 0;
+ return 0;
+ }
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (!b->read_header) {
+ passthrough = true;
+ continue;
+ }
+
+ error = b->read_header(len_p, type_p, b, id);
+
+ switch (error) {
+ case GIT_PASSTHROUGH:
+ passthrough = true;
+ break;
+ case GIT_ENOTFOUND:
+ break;
+ default:
+ return error;
+ }
+ }
+
+ return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND;
+}
+
+int git_odb__read_header_or_object(
+ git_odb_object **out, size_t *len_p, git_otype *type_p,
+ git_odb *db, const git_oid *id)
+{
+ int error = GIT_ENOTFOUND;
+ git_odb_object *object;
+
+ assert(db && id && out && len_p && type_p);
+
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
+ *out = object;
+ return 0;
+ }
+
+ *out = NULL;
+ error = odb_read_header_1(len_p, type_p, db, id, false);
+
+ if (error == GIT_ENOTFOUND && !git_odb_refresh(db))
+ error = odb_read_header_1(len_p, type_p, db, id, true);
+
+ if (error == GIT_ENOTFOUND)
+ return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ);
+
+ /* we found the header; return early */
+ if (!error)
+ return 0;
+
+ if (error == GIT_PASSTHROUGH) {
+ /*
+ * no backend has header-reading functionality
+ * so try using `git_odb_read` instead
+ */
+ error = git_odb_read(&object, db, id);
+ if (!error) {
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
+ *out = object;
+ }
+ }
+
+ return error;
+}
+
+static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id,
+ bool only_refreshed)
+{
+ size_t i;
+ git_rawobj raw;
+ git_odb_object *object;
+ bool found = false;
+
+ if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0)
+ found = true;
+
+ for (i = 0; i < db->backends.length && !found; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (b->read != NULL) {
+ int error = b->read(&raw.data, &raw.len, &raw.type, b, id);
+ if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND)
+ continue;
+
+ if (error < 0)
+ return error;
+
+ found = true;
+ }
+ }
+
+ if (!found)
+ return GIT_ENOTFOUND;
+
+ giterr_clear();
+ if ((object = odb_object__alloc(id, &raw)) == NULL)
+ return -1;
+
+ *out = git_cache_store_raw(odb_cache(db), object);
+ return 0;
+}
+
+int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
+{
+ int error;
+
+ assert(out && db && id);
+
+ *out = git_cache_get_raw(odb_cache(db), id);
+ if (*out != NULL)
+ return 0;
+
+ error = odb_read_1(out, db, id, false);
+
+ if (error == GIT_ENOTFOUND && !git_odb_refresh(db))
+ error = odb_read_1(out, db, id, true);
+
+ if (error == GIT_ENOTFOUND)
+ return git_odb__error_notfound("no match for id", id, GIT_OID_HEXSZ);
+
+ return error;
+}
+
+static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id)
+{
+ git_odb_object *object;
+ size_t _unused;
+ int error;
+
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ *type_p = object->cached.type;
+ return 0;
+ }
+
+ error = odb_read_header_1(&_unused, type_p, db, id, false);
+
+ if (error == GIT_PASSTHROUGH) {
+ error = odb_read_1(&object, db, id, false);
+ if (!error)
+ *type_p = object->cached.type;
+ git_odb_object_free(object);
+ }
+
+ return error;
+}
+
+static int read_prefix_1(git_odb_object **out, git_odb *db,
+ const git_oid *key, size_t len, bool only_refreshed)
+{
+ size_t i;
+ int error = GIT_ENOTFOUND;
+ git_oid found_full_oid = {{0}};
+ git_rawobj raw;
+ void *data = NULL;
+ bool found = false;
+ git_odb_object *object;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (only_refreshed && !b->refresh)
+ continue;
+
+ if (b->read_prefix != NULL) {
+ git_oid full_oid;
+ error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len);
+ if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
+ continue;
+
+ if (error)
+ return error;
+
+ git__free(data);
+ data = raw.data;
+
+ if (found && git_oid__cmp(&full_oid, &found_full_oid)) {
+ git__free(raw.data);
+ return git_odb__error_ambiguous("multiple matches for prefix");
+ }
+
+ found_full_oid = full_oid;
+ found = true;
+ }
+ }
+
+ if (!found)
+ return GIT_ENOTFOUND;
+
+ if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL)
+ return -1;
+
+ *out = git_cache_store_raw(odb_cache(db), object);
+ return 0;
+}
+
+int git_odb_read_prefix(
+ git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len)
+{
+ git_oid key = {{0}};
+ int error;
+
+ assert(out && db);
+
+ if (len < GIT_OID_MINPREFIXLEN)
+ return git_odb__error_ambiguous("prefix length too short");
+
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ if (len == GIT_OID_HEXSZ) {
+ *out = git_cache_get_raw(odb_cache(db), short_id);
+ if (*out != NULL)
+ return 0;
+ }
+
+ git_oid__cpy_prefix(&key, short_id, len);
+
+ error = read_prefix_1(out, db, &key, len, false);
+
+ if (error == GIT_ENOTFOUND && !git_odb_refresh(db))
+ error = read_prefix_1(out, db, &key, len, true);
+
+ if (error == GIT_ENOTFOUND)
+ return git_odb__error_notfound("no match for prefix", &key, len);
+
+ return error;
+}
+
+int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload)
+{
+ unsigned int i;
+ backend_internal *internal;
+
+ git_vector_foreach(&db->backends, i, internal) {
+ git_odb_backend *b = internal->backend;
+ int error = b->foreach(b, cb, payload);
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+int git_odb_write(
+ git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
+{
+ size_t i;
+ int error = GIT_ERROR;
+ git_odb_stream *stream;
+
+ assert(oid && db);
+
+ git_odb_hash(oid, data, len, type);
+ if (odb_freshen(db, oid))
+ return 0;
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ /* we don't write in alternates! */
+ if (internal->is_alternate)
+ continue;
+
+ if (b->write != NULL)
+ error = b->write(b, oid, data, len, type);
+ }
+
+ if (!error || error == GIT_PASSTHROUGH)
+ return 0;
+
+ /* if no backends were able to write the object directly, we try a
+ * streaming write to the backends; just write the whole object into the
+ * stream in one push
+ */
+ if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0)
+ return error;
+
+ stream->write(stream, data, len);
+ error = stream->finalize_write(stream, oid);
+ git_odb_stream_free(stream);
+
+ return error;
+}
+
+static void hash_header(git_hash_ctx *ctx, git_off_t size, git_otype type)
+{
+ char header[64];
+ int hdrlen;
+
+ hdrlen = git_odb__format_object_header(header, sizeof(header), size, type);
+ git_hash_update(ctx, header, hdrlen);
+}
+
+int git_odb_open_wstream(
+ git_odb_stream **stream, git_odb *db, git_off_t size, git_otype type)
+{
+ size_t i, writes = 0;
+ int error = GIT_ERROR;
+ git_hash_ctx *ctx = NULL;
+
+ assert(stream && db);
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ /* we don't write in alternates! */
+ if (internal->is_alternate)
+ continue;
+
+ if (b->writestream != NULL) {
+ ++writes;
+ error = b->writestream(stream, b, size, type);
+ } else if (b->write != NULL) {
+ ++writes;
+ error = init_fake_wstream(stream, b, size, type);
+ }
+ }
+
+ if (error < 0) {
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ else if (!writes)
+ error = git_odb__error_unsupported_in_backend("write object");
+
+ goto done;
+ }
+
+ ctx = git__malloc(sizeof(git_hash_ctx));
+ GITERR_CHECK_ALLOC(ctx);
+
+ if ((error = git_hash_ctx_init(ctx)) < 0)
+ goto done;
+
+ hash_header(ctx, size, type);
+ (*stream)->hash_ctx = ctx;
+
+ (*stream)->declared_size = size;
+ (*stream)->received_bytes = 0;
+
+done:
+ return error;
+}
+
+static int git_odb_stream__invalid_length(
+ const git_odb_stream *stream,
+ const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - "
+ "Invalid length. %"PRIuZ" was expected. The "
+ "total size of the received chunks amounts to %"PRIuZ".",
+ action, stream->declared_size, stream->received_bytes);
+
+ return -1;
+}
+
+int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len)
+{
+ git_hash_update(stream->hash_ctx, buffer, len);
+
+ stream->received_bytes += len;
+
+ if (stream->received_bytes > stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_write()");
+
+ return stream->write(stream, buffer, len);
+}
+
+int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream)
+{
+ if (stream->received_bytes != stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_finalize_write()");
+
+ git_hash_final(out, stream->hash_ctx);
+
+ if (odb_freshen(stream->backend->odb, out))
+ return 0;
+
+ return stream->finalize_write(stream, out);
+}
+
+int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len)
+{
+ return stream->read(stream, buffer, len);
+}
+
+void git_odb_stream_free(git_odb_stream *stream)
+{
+ if (stream == NULL)
+ return;
+
+ git_hash_ctx_cleanup(stream->hash_ctx);
+ git__free(stream->hash_ctx);
+ stream->free(stream);
+}
+
+int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid)
+{
+ size_t i, reads = 0;
+ int error = GIT_ERROR;
+
+ assert(stream && db);
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (b->readstream != NULL) {
+ ++reads;
+ error = b->readstream(stream, b, oid);
+ }
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ if (error < 0 && !reads)
+ error = git_odb__error_unsupported_in_backend("read object streamed");
+
+ return error;
+}
+
+int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_cb progress_cb, void *progress_payload)
+{
+ size_t i, writes = 0;
+ int error = GIT_ERROR;
+
+ assert(out && db);
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ /* we don't write in alternates! */
+ if (internal->is_alternate)
+ continue;
+
+ if (b->writepack != NULL) {
+ ++writes;
+ error = b->writepack(out, b, db, progress_cb, progress_payload);
+ }
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ if (error < 0 && !writes)
+ error = git_odb__error_unsupported_in_backend("write pack");
+
+ return error;
+}
+
+void *git_odb_backend_malloc(git_odb_backend *backend, size_t len)
+{
+ GIT_UNUSED(backend);
+ return git__malloc(len);
+}
+
+int git_odb_refresh(struct git_odb *db)
+{
+ size_t i;
+ assert(db);
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (b->refresh != NULL) {
+ int error = b->refresh(b);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+int git_odb__error_notfound(
+ const char *message, const git_oid *oid, size_t oid_len)
+{
+ if (oid != NULL) {
+ char oid_str[GIT_OID_HEXSZ + 1];
+ git_oid_tostr(oid_str, oid_len+1, oid);
+ giterr_set(GITERR_ODB, "Object not found - %s (%.*s)",
+ message, (int) oid_len, oid_str);
+ } else
+ giterr_set(GITERR_ODB, "Object not found - %s", message);
+
+ return GIT_ENOTFOUND;
+}
+
+int git_odb__error_ambiguous(const char *message)
+{
+ giterr_set(GITERR_ODB, "Ambiguous SHA1 prefix - %s", message);
+ return GIT_EAMBIGUOUS;
+}
+
+int git_odb_init_backend(git_odb_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_odb_h__
+#define INCLUDE_odb_h__
+
+#include "git2/odb.h"
+#include "git2/oid.h"
+#include "git2/types.h"
+
+#include "vector.h"
+#include "cache.h"
+#include "posix.h"
+#include "filter.h"
+
+#define GIT_OBJECTS_DIR "objects/"
+#define GIT_OBJECT_DIR_MODE 0777
+#define GIT_OBJECT_FILE_MODE 0444
+
+/* DO NOT EXPORT */
+typedef struct {
+ void *data; /**< Raw, decompressed object data. */
+ size_t len; /**< Total number of bytes in data. */
+ git_otype type; /**< Type of this object. */
+} git_rawobj;
+
+/* EXPORT */
+struct git_odb_object {
+ git_cached_obj cached;
+ void *buffer;
+};
+
+/* EXPORT */
+struct git_odb {
+ git_refcount rc;
+ git_vector backends;
+ git_cache own_cache;
+};
+
+/*
+ * Hash a git_rawobj internally.
+ * The `git_rawobj` is supposed to be previously initialized
+ */
+int git_odb__hashobj(git_oid *id, git_rawobj *obj);
+
+/*
+ * Format the object header such as it would appear in the on-disk object
+ */
+int git_odb__format_object_header(char *hdr, size_t n, git_off_t obj_len, git_otype obj_type);
+/*
+ * Hash an open file descriptor.
+ * This is a performance call when the contents of a fd need to be hashed,
+ * but the fd is already open and we have the size of the contents.
+ *
+ * Saves us some `stat` calls.
+ *
+ * The fd is never closed, not even on error. It must be opened and closed
+ * by the caller
+ */
+int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type);
+
+/*
+ * Hash an open file descriptor applying an array of filters
+ * Acts just like git_odb__hashfd with the addition of filters...
+ */
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t len, git_otype type, git_filter_list *fl);
+
+/*
+ * Hash a `path`, assuming it could be a POSIX symlink: if the path is a
+ * symlink, then the raw contents of the symlink will be hashed. Otherwise,
+ * this will fallback to `git_odb__hashfd`.
+ *
+ * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may
+ * only point to blobs.
+ */
+int git_odb__hashlink(git_oid *out, const char *path);
+
+/*
+ * Generate a GIT_ENOTFOUND error for the ODB.
+ */
+int git_odb__error_notfound(
+ const char *message, const git_oid *oid, size_t oid_len);
+
+/*
+ * Generate a GIT_EAMBIGUOUS error for the ODB.
+ */
+int git_odb__error_ambiguous(const char *message);
+
+/*
+ * Attempt to read object header or just return whole object if it could
+ * not be read.
+ */
+int git_odb__read_header_or_object(
+ git_odb_object **out, size_t *len_p, git_otype *type_p,
+ git_odb *db, const git_oid *id);
+
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_odb_object__free(void *object);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include <zlib.h>
+#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
+#include "fileops.h"
+#include "hash.h"
+#include "odb.h"
+#include "delta.h"
+#include "filebuf.h"
+
+#include "git2/odb_backend.h"
+#include "git2/types.h"
+
+typedef struct { /* object header data */
+ git_otype type; /* object type */
+ size_t size; /* object size */
+} obj_hdr;
+
+typedef struct {
+ git_odb_stream stream;
+ git_filebuf fbuf;
+} loose_writestream;
+
+typedef struct loose_backend {
+ git_odb_backend parent;
+
+ int object_zlib_level; /** loose object zlib compression level. */
+ int fsync_object_files; /** loose object file fsync flag. */
+ mode_t object_file_mode;
+ mode_t object_dir_mode;
+
+ size_t objects_dirlen;
+ char objects_dir[GIT_FLEX_ARRAY];
+} loose_backend;
+
+/* State structure for exploring directories,
+ * in order to locate objects matching a short oid.
+ */
+typedef struct {
+ size_t dir_len;
+ unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */
+ size_t short_oid_len;
+ int found; /* number of matching
+ * objects already found */
+ unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of
+ * the object found */
+} loose_locate_object_state;
+
+
+/***********************************************************
+ *
+ * MISCELLANEOUS HELPER FUNCTIONS
+ *
+ ***********************************************************/
+
+static int object_file_name(
+ git_buf *name, const loose_backend *be, const git_oid *id)
+{
+ size_t alloclen;
+
+ /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */
+ GITERR_CHECK_ALLOC_ADD(&alloclen, be->objects_dirlen, GIT_OID_HEXSZ);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 3);
+ if (git_buf_grow(name, alloclen) < 0)
+ return -1;
+
+ git_buf_set(name, be->objects_dir, be->objects_dirlen);
+ git_path_to_dir(name);
+
+ /* loose object filename: aa/aaa... (41 bytes) */
+ git_oid_pathfmt(name->ptr + name->size, id);
+ name->size += GIT_OID_HEXSZ + 1;
+ name->ptr[name->size] = '\0';
+
+ return 0;
+}
+
+static int object_mkdir(const git_buf *name, const loose_backend *be)
+{
+ return git_futils_mkdir_relative(
+ name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL);
+}
+
+static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
+{
+ unsigned long c;
+ unsigned char *data = (unsigned char *)obj->ptr;
+ size_t shift, size, used = 0;
+
+ if (git_buf_len(obj) == 0)
+ return 0;
+
+ c = data[used++];
+ hdr->type = (c >> 4) & 7;
+
+ size = c & 15;
+ shift = 4;
+ while (c & 0x80) {
+ if (git_buf_len(obj) <= used)
+ return 0;
+ if (sizeof(size_t) * 8 <= shift)
+ return 0;
+ c = data[used++];
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ hdr->size = size;
+
+ return used;
+}
+
+static size_t get_object_header(obj_hdr *hdr, unsigned char *data)
+{
+ char c, typename[10];
+ size_t size, used = 0;
+
+ /*
+ * type name string followed by space.
+ */
+ while ((c = data[used]) != ' ') {
+ typename[used++] = c;
+ if (used >= sizeof(typename))
+ return 0;
+ }
+ typename[used] = 0;
+ if (used == 0)
+ return 0;
+ hdr->type = git_object_string2type(typename);
+ used++; /* consume the space */
+
+ /*
+ * length follows immediately in decimal (without
+ * leading zeros).
+ */
+ size = data[used++] - '0';
+ if (size > 9)
+ return 0;
+ if (size) {
+ while ((c = data[used]) != '\0') {
+ size_t d = c - '0';
+ if (d > 9)
+ break;
+ used++;
+ size = size * 10 + d;
+ }
+ }
+ hdr->size = size;
+
+ /*
+ * the length must be followed by a zero byte
+ */
+ if (data[used++] != '\0')
+ return 0;
+
+ return used;
+}
+
+
+
+/***********************************************************
+ *
+ * ZLIB RELATED FUNCTIONS
+ *
+ ***********************************************************/
+
+static void init_stream(z_stream *s, void *out, size_t len)
+{
+ memset(s, 0, sizeof(*s));
+ s->next_out = out;
+ s->avail_out = (uInt)len;
+}
+
+static void set_stream_input(z_stream *s, void *in, size_t len)
+{
+ s->next_in = in;
+ s->avail_in = (uInt)len;
+}
+
+static void set_stream_output(z_stream *s, void *out, size_t len)
+{
+ s->next_out = out;
+ s->avail_out = (uInt)len;
+}
+
+
+static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len)
+{
+ int status;
+
+ init_stream(s, out, len);
+ set_stream_input(s, obj->ptr, git_buf_len(obj));
+
+ if ((status = inflateInit(s)) < Z_OK)
+ return status;
+
+ return inflate(s, 0);
+}
+
+static int finish_inflate(z_stream *s)
+{
+ int status = Z_OK;
+
+ while (status == Z_OK)
+ status = inflate(s, Z_FINISH);
+
+ inflateEnd(s);
+
+ if ((status != Z_STREAM_END) || (s->avail_in != 0)) {
+ giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int is_zlib_compressed_data(unsigned char *data)
+{
+ unsigned int w;
+
+ w = ((unsigned int)(data[0]) << 8) + data[1];
+ return (data[0] & 0x8F) == 0x08 && !(w % 31);
+}
+
+static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
+{
+ z_stream zs;
+ int status = Z_OK;
+
+ memset(&zs, 0x0, sizeof(zs));
+
+ zs.next_out = out;
+ zs.avail_out = (uInt)outlen;
+
+ zs.next_in = in;
+ zs.avail_in = (uInt)inlen;
+
+ if (inflateInit(&zs) < Z_OK) {
+ giterr_set(GITERR_ZLIB, "Failed to inflate buffer");
+ return -1;
+ }
+
+ while (status == Z_OK)
+ status = inflate(&zs, Z_FINISH);
+
+ inflateEnd(&zs);
+
+ if (status != Z_STREAM_END /* || zs.avail_in != 0 */ ||
+ zs.total_out != outlen)
+ {
+ giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
+{
+ unsigned char *buf, *head = hb;
+ size_t tail, alloc_size;
+
+ /*
+ * allocate a buffer to hold the inflated data and copy the
+ * initial sequence of inflated data from the tail of the
+ * head buffer, if any.
+ */
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr->size, 1) ||
+ (buf = git__malloc(alloc_size)) == NULL) {
+ inflateEnd(s);
+ return NULL;
+ }
+ tail = s->total_out - used;
+ if (used > 0 && tail > 0) {
+ if (tail > hdr->size)
+ tail = hdr->size;
+ memcpy(buf, head + used, tail);
+ }
+ used = tail;
+
+ /*
+ * inflate the remainder of the object data, if any
+ */
+ if (hdr->size < used)
+ inflateEnd(s);
+ else {
+ set_stream_output(s, buf + used, hdr->size - used);
+ if (finish_inflate(s)) {
+ git__free(buf);
+ return NULL;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * At one point, there was a loose object format that was intended to
+ * mimic the format used in pack-files. This was to allow easy copying
+ * of loose object data into packs. This format is no longer used, but
+ * we must still read it.
+ */
+static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj)
+{
+ unsigned char *in, *buf;
+ obj_hdr hdr;
+ size_t len, used, alloclen;
+
+ /*
+ * read the object header, which is an (uncompressed)
+ * binary encoding of the object type and size.
+ */
+ if ((used = get_binary_object_header(&hdr, obj)) == 0 ||
+ !git_object_typeisloose(hdr.type)) {
+ giterr_set(GITERR_ODB, "Failed to inflate loose object.");
+ return -1;
+ }
+
+ /*
+ * allocate a buffer and inflate the data into it
+ */
+ GITERR_CHECK_ALLOC_ADD(&alloclen, hdr.size, 1);
+ buf = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(buf);
+
+ in = ((unsigned char *)obj->ptr) + used;
+ len = obj->size - used;
+ if (inflate_buffer(in, len, buf, hdr.size) < 0) {
+ git__free(buf);
+ return -1;
+ }
+ buf[hdr.size] = '\0';
+
+ out->data = buf;
+ out->len = hdr.size;
+ out->type = hdr.type;
+
+ return 0;
+}
+
+static int inflate_disk_obj(git_rawobj *out, git_buf *obj)
+{
+ unsigned char head[64], *buf;
+ z_stream zs;
+ obj_hdr hdr;
+ size_t used;
+
+ /*
+ * check for a pack-like loose object
+ */
+ if (!is_zlib_compressed_data((unsigned char *)obj->ptr))
+ return inflate_packlike_loose_disk_obj(out, obj);
+
+ /*
+ * inflate the initial part of the io buffer in order
+ * to parse the object header (type and size).
+ */
+ if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK ||
+ (used = get_object_header(&hdr, head)) == 0 ||
+ !git_object_typeisloose(hdr.type))
+ {
+ giterr_set(GITERR_ODB, "Failed to inflate disk object.");
+ return -1;
+ }
+
+ /*
+ * allocate a buffer and inflate the object data into it
+ * (including the initial sequence in the head buffer).
+ */
+ if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL)
+ return -1;
+ buf[hdr.size] = '\0';
+
+ out->data = buf;
+ out->len = hdr.size;
+ out->type = hdr.type;
+
+ return 0;
+}
+
+
+
+
+
+
+/***********************************************************
+ *
+ * ODB OBJECT READING & WRITING
+ *
+ * Backend for the public API; read headers and full objects
+ * from the ODB. Write raw data to the ODB.
+ *
+ ***********************************************************/
+
+static int read_loose(git_rawobj *out, git_buf *loc)
+{
+ int error;
+ git_buf obj = GIT_BUF_INIT;
+
+ assert(out && loc);
+
+ if (git_buf_oom(loc))
+ return -1;
+
+ out->data = NULL;
+ out->len = 0;
+ out->type = GIT_OBJ_BAD;
+
+ if (!(error = git_futils_readbuffer(&obj, loc->ptr)))
+ error = inflate_disk_obj(out, &obj);
+
+ git_buf_free(&obj);
+
+ return error;
+}
+
+static int read_header_loose(git_rawobj *out, git_buf *loc)
+{
+ int error = 0, z_return = Z_ERRNO, read_bytes;
+ git_file fd;
+ z_stream zs;
+ obj_hdr header_obj;
+ unsigned char raw_buffer[16], inflated_buffer[64];
+
+ assert(out && loc);
+
+ if (git_buf_oom(loc))
+ return -1;
+
+ out->data = NULL;
+
+ if ((fd = git_futils_open_ro(loc->ptr)) < 0)
+ return fd;
+
+ init_stream(&zs, inflated_buffer, sizeof(inflated_buffer));
+
+ z_return = inflateInit(&zs);
+
+ while (z_return == Z_OK) {
+ if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
+ set_stream_input(&zs, raw_buffer, read_bytes);
+ z_return = inflate(&zs, 0);
+ } else
+ z_return = Z_STREAM_END;
+ }
+
+ if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR)
+ || get_object_header(&header_obj, inflated_buffer) == 0
+ || git_object_typeisloose(header_obj.type) == 0)
+ {
+ giterr_set(GITERR_ZLIB, "Failed to read loose object header");
+ error = -1;
+ } else {
+ out->len = header_obj.size;
+ out->type = header_obj.type;
+ }
+
+ finish_inflate(&zs);
+ p_close(fd);
+
+ return error;
+}
+
+static int locate_object(
+ git_buf *object_location,
+ loose_backend *backend,
+ const git_oid *oid)
+{
+ int error = object_file_name(object_location, backend, oid);
+
+ if (!error && !git_path_exists(object_location->ptr))
+ return GIT_ENOTFOUND;
+
+ return error;
+}
+
+/* Explore an entry of a directory and see if it matches a short oid */
+static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) {
+ loose_locate_object_state *sstate = (loose_locate_object_state *)state;
+
+ if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) {
+ /* Entry cannot be an object. Continue to next entry */
+ return 0;
+ }
+
+ if (git_path_isdir(pathbuf->ptr) == false) {
+ /* We are already in the directory matching the 2 first hex characters,
+ * compare the first ncmp characters of the oids */
+ if (!memcmp(sstate->short_oid + 2,
+ (unsigned char *)pathbuf->ptr + sstate->dir_len,
+ sstate->short_oid_len - 2)) {
+
+ if (!sstate->found) {
+ sstate->res_oid[0] = sstate->short_oid[0];
+ sstate->res_oid[1] = sstate->short_oid[1];
+ memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2);
+ }
+ sstate->found++;
+ }
+ }
+
+ if (sstate->found > 1)
+ return GIT_EAMBIGUOUS;
+
+ return 0;
+}
+
+/* Locate an object matching a given short oid */
+static int locate_object_short_oid(
+ git_buf *object_location,
+ git_oid *res_oid,
+ loose_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ char *objects_dir = backend->objects_dir;
+ size_t dir_len = strlen(objects_dir), alloc_len;
+ loose_locate_object_state state;
+ int error;
+
+ /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 3);
+ if (git_buf_grow(object_location, alloc_len) < 0)
+ return -1;
+
+ git_buf_set(object_location, objects_dir, dir_len);
+ git_path_to_dir(object_location);
+
+ /* save adjusted position at end of dir so it can be restored later */
+ dir_len = git_buf_len(object_location);
+
+ /* Convert raw oid to hex formatted oid */
+ git_oid_fmt((char *)state.short_oid, short_oid);
+
+ /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
+ if (git_buf_put(object_location, (char *)state.short_oid, 3) < 0)
+ return -1;
+ object_location->ptr[object_location->size - 1] = '/';
+
+ /* Check that directory exists */
+ if (git_path_isdir(object_location->ptr) == false)
+ return git_odb__error_notfound("no matching loose object for prefix",
+ short_oid, len);
+
+ state.dir_len = git_buf_len(object_location);
+ state.short_oid_len = len;
+ state.found = 0;
+
+ /* Explore directory to find a unique object matching short_oid */
+ error = git_path_direach(
+ object_location, 0, fn_locate_object_short_oid, &state);
+ if (error < 0 && error != GIT_EAMBIGUOUS)
+ return error;
+
+ if (!state.found)
+ return git_odb__error_notfound("no matching loose object for prefix",
+ short_oid, len);
+
+ if (state.found > 1)
+ return git_odb__error_ambiguous("multiple matches in loose objects");
+
+ /* Convert obtained hex formatted oid to raw */
+ error = git_oid_fromstr(res_oid, (char *)state.res_oid);
+ if (error)
+ return error;
+
+ /* Update the location according to the oid obtained */
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
+
+ git_buf_truncate(object_location, dir_len);
+ if (git_buf_grow(object_location, alloc_len) < 0)
+ return -1;
+
+ git_oid_pathfmt(object_location->ptr + dir_len, res_oid);
+
+ object_location->size += GIT_OID_HEXSZ + 1;
+ object_location->ptr[object_location->size] = '\0';
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+/***********************************************************
+ *
+ * LOOSE BACKEND PUBLIC API
+ *
+ * Implement the git_odb_backend API calls
+ *
+ ***********************************************************/
+
+static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ git_buf object_path = GIT_BUF_INIT;
+ git_rawobj raw;
+ int error;
+
+ assert(backend && oid);
+
+ raw.len = 0;
+ raw.type = GIT_OBJ_BAD;
+
+ if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) {
+ error = git_odb__error_notfound("no matching loose object",
+ oid, GIT_OID_HEXSZ);
+ } else if ((error = read_header_loose(&raw, &object_path)) == 0) {
+ *len_p = raw.len;
+ *type_p = raw.type;
+ }
+
+ git_buf_free(&object_path);
+
+ return error;
+}
+
+static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ git_buf object_path = GIT_BUF_INIT;
+ git_rawobj raw;
+ int error = 0;
+
+ assert(backend && oid);
+
+ if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) {
+ error = git_odb__error_notfound("no matching loose object",
+ oid, GIT_OID_HEXSZ);
+ } else if ((error = read_loose(&raw, &object_path)) == 0) {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+ }
+
+ git_buf_free(&object_path);
+
+ return error;
+}
+
+static int loose_backend__read_prefix(
+ git_oid *out_oid,
+ void **buffer_p,
+ size_t *len_p,
+ git_otype *type_p,
+ git_odb_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ int error = 0;
+
+ assert(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ);
+
+ if (len == GIT_OID_HEXSZ) {
+ /* We can fall back to regular read method */
+ error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
+ if (!error)
+ git_oid_cpy(out_oid, short_oid);
+ } else {
+ git_buf object_path = GIT_BUF_INIT;
+ git_rawobj raw;
+
+ assert(backend && short_oid);
+
+ if ((error = locate_object_short_oid(&object_path, out_oid,
+ (loose_backend *)backend, short_oid, len)) == 0 &&
+ (error = read_loose(&raw, &object_path)) == 0)
+ {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+ }
+
+ git_buf_free(&object_path);
+ }
+
+ return error;
+}
+
+static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
+{
+ git_buf object_path = GIT_BUF_INIT;
+ int error;
+
+ assert(backend && oid);
+
+ error = locate_object(&object_path, (loose_backend *)backend, oid);
+
+ git_buf_free(&object_path);
+
+ return !error;
+}
+
+static int loose_backend__exists_prefix(
+ git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
+{
+ git_buf object_path = GIT_BUF_INIT;
+ int error;
+
+ assert(backend && out && short_id && len >= GIT_OID_MINPREFIXLEN);
+
+ error = locate_object_short_oid(
+ &object_path, out, (loose_backend *)backend, short_id, len);
+
+ git_buf_free(&object_path);
+
+ return error;
+}
+
+struct foreach_state {
+ size_t dir_len;
+ git_odb_foreach_cb cb;
+ void *data;
+};
+
+GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
+{
+ int v, i = 0;
+ if (strlen(ptr) != GIT_OID_HEXSZ+1)
+ return -1;
+
+ if (ptr[2] != '/') {
+ return -1;
+ }
+
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[0] = (unsigned char) v;
+
+ ptr += 3;
+ for (i = 0; i < 38; i += 2) {
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[1 + i/2] = (unsigned char) v;
+ }
+
+ return 0;
+}
+
+static int foreach_object_dir_cb(void *_state, git_buf *path)
+{
+ git_oid oid;
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)
+ return 0;
+
+ return giterr_set_after_callback_function(
+ state->cb(&oid, state->data), "git_odb_foreach");
+}
+
+static int foreach_cb(void *_state, git_buf *path)
+{
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ /* non-dir is some stray file, ignore it */
+ if (!git_path_isdir(git_buf_cstr(path)))
+ return 0;
+
+ return git_path_direach(path, 0, foreach_object_dir_cb, state);
+}
+
+static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
+{
+ char *objects_dir;
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ struct foreach_state state;
+ loose_backend *backend = (loose_backend *) _backend;
+
+ assert(backend && cb);
+
+ objects_dir = backend->objects_dir;
+
+ git_buf_sets(&buf, objects_dir);
+ git_path_to_dir(&buf);
+ if (git_buf_oom(&buf))
+ return -1;
+
+ memset(&state, 0, sizeof(state));
+ state.cb = cb;
+ state.data = data;
+ state.dir_len = git_buf_len(&buf);
+
+ error = git_path_direach(&buf, 0, foreach_cb, &state);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid)
+{
+ loose_writestream *stream = (loose_writestream *)_stream;
+ loose_backend *backend = (loose_backend *)_stream->backend;
+ git_buf final_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if (object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0)
+ error = -1;
+ else
+ error = git_filebuf_commit_at(
+ &stream->fbuf, final_path.ptr);
+
+ git_buf_free(&final_path);
+
+ return error;
+}
+
+static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
+{
+ loose_writestream *stream = (loose_writestream *)_stream;
+ return git_filebuf_write(&stream->fbuf, data, len);
+}
+
+static void loose_backend__stream_free(git_odb_stream *_stream)
+{
+ loose_writestream *stream = (loose_writestream *)_stream;
+
+ git_filebuf_cleanup(&stream->fbuf);
+ git__free(stream);
+}
+
+static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, git_off_t length, git_otype type)
+{
+ loose_backend *backend;
+ loose_writestream *stream = NULL;
+ char hdr[64];
+ git_buf tmp_path = GIT_BUF_INIT;
+ int hdrlen;
+
+ assert(_backend && length >= 0);
+
+ backend = (loose_backend *)_backend;
+ *stream_out = NULL;
+
+ hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type);
+
+ stream = git__calloc(1, sizeof(loose_writestream));
+ GITERR_CHECK_ALLOC(stream);
+
+ stream->stream.backend = _backend;
+ stream->stream.read = NULL; /* read only */
+ stream->stream.write = &loose_backend__stream_write;
+ stream->stream.finalize_write = &loose_backend__stream_fwrite;
+ stream->stream.free = &loose_backend__stream_free;
+ stream->stream.mode = GIT_STREAM_WRONLY;
+
+ if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 ||
+ git_filebuf_open(&stream->fbuf, tmp_path.ptr,
+ GIT_FILEBUF_TEMPORARY |
+ (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
+ backend->object_file_mode) < 0 ||
+ stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0)
+ {
+ git_filebuf_cleanup(&stream->fbuf);
+ git__free(stream);
+ stream = NULL;
+ }
+ git_buf_free(&tmp_path);
+ *stream_out = (git_odb_stream *)stream;
+
+ return !stream ? -1 : 0;
+}
+
+static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type)
+{
+ int error = 0, header_len;
+ git_buf final_path = GIT_BUF_INIT;
+ char header[64];
+ git_filebuf fbuf = GIT_FILEBUF_INIT;
+ loose_backend *backend;
+
+ backend = (loose_backend *)_backend;
+
+ /* prepare the header for the file */
+ header_len = git_odb__format_object_header(header, sizeof(header), len, type);
+
+ if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
+ git_filebuf_open(&fbuf, final_path.ptr,
+ GIT_FILEBUF_TEMPORARY |
+ (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
+ backend->object_file_mode) < 0)
+ {
+ error = -1;
+ goto cleanup;
+ }
+
+ git_filebuf_write(&fbuf, header, header_len);
+ git_filebuf_write(&fbuf, data, len);
+
+ if (object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0 ||
+ git_filebuf_commit_at(&fbuf, final_path.ptr) < 0)
+ error = -1;
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&fbuf);
+ git_buf_free(&final_path);
+ return error;
+}
+
+static int loose_backend__freshen(
+ git_odb_backend *_backend,
+ const git_oid *oid)
+{
+ loose_backend *backend = (loose_backend *)_backend;
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ if (object_file_name(&path, backend, oid) < 0)
+ return -1;
+
+ error = git_futils_touch(path.ptr, NULL);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static void loose_backend__free(git_odb_backend *_backend)
+{
+ loose_backend *backend;
+ assert(_backend);
+ backend = (loose_backend *)_backend;
+
+ git__free(backend);
+}
+
+int git_odb_backend_loose(
+ git_odb_backend **backend_out,
+ const char *objects_dir,
+ int compression_level,
+ int do_fsync,
+ unsigned int dir_mode,
+ unsigned int file_mode)
+{
+ loose_backend *backend;
+ size_t objects_dirlen, alloclen;
+
+ assert(backend_out && objects_dir);
+
+ objects_dirlen = strlen(objects_dir);
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(loose_backend), objects_dirlen);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 2);
+ backend = git__calloc(1, alloclen);
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
+ backend->objects_dirlen = objects_dirlen;
+ memcpy(backend->objects_dir, objects_dir, objects_dirlen);
+ if (backend->objects_dir[backend->objects_dirlen - 1] != '/')
+ backend->objects_dir[backend->objects_dirlen++] = '/';
+
+ if (compression_level < 0)
+ compression_level = Z_BEST_SPEED;
+
+ if (dir_mode == 0)
+ dir_mode = GIT_OBJECT_DIR_MODE;
+
+ if (file_mode == 0)
+ file_mode = GIT_OBJECT_FILE_MODE;
+
+ backend->object_zlib_level = compression_level;
+ backend->fsync_object_files = do_fsync;
+ backend->object_dir_mode = dir_mode;
+ backend->object_file_mode = file_mode;
+
+ backend->parent.read = &loose_backend__read;
+ backend->parent.write = &loose_backend__write;
+ backend->parent.read_prefix = &loose_backend__read_prefix;
+ backend->parent.read_header = &loose_backend__read_header;
+ backend->parent.writestream = &loose_backend__stream;
+ backend->parent.exists = &loose_backend__exists;
+ backend->parent.exists_prefix = &loose_backend__exists_prefix;
+ backend->parent.foreach = &loose_backend__foreach;
+ backend->parent.freshen = &loose_backend__freshen;
+ backend->parent.free = &loose_backend__free;
+
+ *backend_out = (git_odb_backend *)backend;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
+#include "fileops.h"
+#include "hash.h"
+#include "odb.h"
+#include "array.h"
+#include "oidmap.h"
+
+#include "git2/odb_backend.h"
+#include "git2/types.h"
+#include "git2/pack.h"
+
+GIT__USE_OIDMAP
+
+struct memobject {
+ git_oid oid;
+ size_t len;
+ git_otype type;
+ char data[GIT_FLEX_ARRAY];
+};
+
+struct memory_packer_db {
+ git_odb_backend parent;
+ git_oidmap *objects;
+ git_array_t(struct memobject *) commits;
+};
+
+static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+ size_t alloc_len;
+ int rval;
+
+ pos = kh_put(oid, db->objects, oid, &rval);
+ if (rval < 0)
+ return -1;
+
+ if (rval == 0)
+ return 0;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len);
+ obj = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(obj);
+
+ memcpy(obj->data, data, len);
+ git_oid_cpy(&obj->oid, oid);
+ obj->len = len;
+ obj->type = type;
+
+ kh_key(db->objects, pos) = &obj->oid;
+ kh_val(db->objects, pos) = obj;
+
+ if (type == GIT_OBJ_COMMIT) {
+ struct memobject **store = git_array_alloc(db->commits);
+ GITERR_CHECK_ALLOC(store);
+ *store = obj;
+ }
+
+ return 0;
+}
+
+static int impl__exists(git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos != kh_end(db->objects))
+ return 1;
+
+ return 0;
+}
+
+static int impl__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos == kh_end(db->objects))
+ return GIT_ENOTFOUND;
+
+ obj = kh_val(db->objects, pos);
+
+ *len_p = obj->len;
+ *type_p = obj->type;
+ *buffer_p = git__malloc(obj->len);
+ GITERR_CHECK_ALLOC(*buffer_p);
+
+ memcpy(*buffer_p, obj->data, obj->len);
+ return 0;
+}
+
+static int impl__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos == kh_end(db->objects))
+ return GIT_ENOTFOUND;
+
+ obj = kh_val(db->objects, pos);
+
+ *len_p = obj->len;
+ *type_p = obj->type;
+ return 0;
+}
+
+int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *_backend)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ git_packbuilder *packbuilder;
+ uint32_t i;
+ int err = -1;
+
+ if (git_packbuilder_new(&packbuilder, repo) < 0)
+ return -1;
+
+ for (i = 0; i < db->commits.size; ++i) {
+ struct memobject *commit = db->commits.ptr[i];
+
+ err = git_packbuilder_insert_commit(packbuilder, &commit->oid);
+ if (err < 0)
+ goto cleanup;
+ }
+
+ err = git_packbuilder_write_buf(pack, packbuilder);
+
+cleanup:
+ git_packbuilder_free(packbuilder);
+ return err;
+}
+
+void git_mempack_reset(git_odb_backend *_backend)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ struct memobject *object = NULL;
+
+ kh_foreach_value(db->objects, object, {
+ git__free(object);
+ });
+
+ git_array_clear(db->commits);
+
+ git_oidmap_clear(db->objects);
+}
+
+static void impl__free(git_odb_backend *_backend)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+
+ git_oidmap_free(db->objects);
+ git__free(db);
+}
+
+int git_mempack_new(git_odb_backend **out)
+{
+ struct memory_packer_db *db;
+
+ assert(out);
+
+ db = git__calloc(1, sizeof(struct memory_packer_db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->objects = git_oidmap_alloc();
+
+ db->parent.version = GIT_ODB_BACKEND_VERSION;
+ db->parent.read = &impl__read;
+ db->parent.write = &impl__write;
+ db->parent.read_header = &impl__read_header;
+ db->parent.exists = &impl__exists;
+ db->parent.free = &impl__free;
+
+ *out = (git_odb_backend *)db;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include <zlib.h>
+#include "git2/repository.h"
+#include "git2/indexer.h"
+#include "git2/sys/odb_backend.h"
+#include "fileops.h"
+#include "hash.h"
+#include "odb.h"
+#include "delta.h"
+#include "sha1_lookup.h"
+#include "mwindow.h"
+#include "pack.h"
+
+#include "git2/odb_backend.h"
+
+/* re-freshen pack files no more than every 2 seconds */
+#define FRESHEN_FREQUENCY 2
+
+struct pack_backend {
+ git_odb_backend parent;
+ git_vector packs;
+ struct git_pack_file *last_found;
+ char *pack_folder;
+};
+
+struct pack_writepack {
+ struct git_odb_writepack parent;
+ git_indexer *indexer;
+};
+
+/**
+ * The wonderful tale of a Packed Object lookup query
+ * ===================================================
+ * A riveting and epic story of epicness and ASCII
+ * art, presented by yours truly,
+ * Sir Vicent of Marti
+ *
+ *
+ * Chapter 1: Once upon a time...
+ * Initialization of the Pack Backend
+ * --------------------------------------------------
+ *
+ * # git_odb_backend_pack
+ * | Creates the pack backend structure, initializes the
+ * | callback pointers to our default read() and exist() methods,
+ * | and tries to preload all the known packfiles in the ODB.
+ * |
+ * |-# packfile_load_all
+ * | Tries to find the `pack` folder, if it exists. ODBs without
+ * | a pack folder are ignored altogether. If there's a `pack` folder
+ * | we run a `dirent` callback through every file in the pack folder
+ * | to find our packfiles. The packfiles are then sorted according
+ * | to a sorting callback.
+ * |
+ * |-# packfile_load__cb
+ * | | This callback is called from `dirent` with every single file
+ * | | inside the pack folder. We find the packs by actually locating
+ * | | their index (ends in ".idx"). From that index, we verify that
+ * | | the corresponding packfile exists and is valid, and if so, we
+ * | | add it to the pack list.
+ * | |
+ * | |-# packfile_check
+ * | Make sure that there's a packfile to back this index, and store
+ * | some very basic information regarding the packfile itself,
+ * | such as the full path, the size, and the modification time.
+ * | We don't actually open the packfile to check for internal consistency.
+ * |
+ * |-# packfile_sort__cb
+ * Sort all the preloaded packs according to some specific criteria:
+ * we prioritize the "newer" packs because it's more likely they
+ * contain the objects we are looking for, and we prioritize local
+ * packs over remote ones.
+ *
+ *
+ *
+ * Chapter 2: To be, or not to be...
+ * A standard packed `exist` query for an OID
+ * --------------------------------------------------
+ *
+ * # pack_backend__exists
+ * | Check if the given SHA1 oid exists in any of the packs
+ * | that have been loaded for our ODB.
+ * |
+ * |-# pack_entry_find
+ * | Iterate through all the packs that have been preloaded
+ * | (starting by the pack where the latest object was found)
+ * | to try to find the OID in one of them.
+ * |
+ * |-# pack_entry_find1
+ * | Check the index of an individual pack to see if the SHA1
+ * | OID can be found. If we can find the offset to that SHA1
+ * | inside of the index, that means the object is contained
+ * | inside of the packfile and we can stop searching.
+ * | Before returning, we verify that the packfile behing the
+ * | index we are searching still exists on disk.
+ * |
+ * |-# pack_entry_find_offset
+ * | | Mmap the actual index file to disk if it hasn't been opened
+ * | | yet, and run a binary search through it to find the OID.
+ * | | See <http://book.git-scm.com/7_the_packfile.html> for specifics
+ * | | on the Packfile Index format and how do we find entries in it.
+ * | |
+ * | |-# pack_index_open
+ * | | Guess the name of the index based on the full path to the
+ * | | packfile, open it and verify its contents. Only if the index
+ * | | has not been opened already.
+ * | |
+ * | |-# pack_index_check
+ * | Mmap the index file and do a quick run through the header
+ * | to guess the index version (right now we support v1 and v2),
+ * | and to verify that the size of the index makes sense.
+ * |
+ * |-# packfile_open
+ * See `packfile_open` in Chapter 3
+ *
+ *
+ *
+ * Chapter 3: The neverending story...
+ * A standard packed `lookup` query for an OID
+ * --------------------------------------------------
+ * TODO
+ *
+ */
+
+
+/***********************************************************
+ *
+ * FORWARD DECLARATIONS
+ *
+ ***********************************************************/
+
+static int packfile_sort__cb(const void *a_, const void *b_);
+
+static int packfile_load__cb(void *_data, git_buf *path);
+
+static int pack_entry_find(struct git_pack_entry *e,
+ struct pack_backend *backend, const git_oid *oid);
+
+/* Can find the offset of an object given
+ * a prefix of an identifier.
+ * Sets GIT_EAMBIGUOUS if short oid is ambiguous.
+ * This method assumes that len is between
+ * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ.
+ */
+static int pack_entry_find_prefix(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len);
+
+
+
+/***********************************************************
+ *
+ * PACK WINDOW MANAGEMENT
+ *
+ ***********************************************************/
+
+static int packfile_sort__cb(const void *a_, const void *b_)
+{
+ const struct git_pack_file *a = a_;
+ const struct git_pack_file *b = b_;
+ int st;
+
+ /*
+ * Local packs tend to contain objects specific to our
+ * variant of the project than remote ones. In addition,
+ * remote ones could be on a network mounted filesystem.
+ * Favor local ones for these reasons.
+ */
+ st = a->pack_local - b->pack_local;
+ if (st)
+ return -st;
+
+ /*
+ * Younger packs tend to contain more recent objects,
+ * and more recent objects tend to get accessed more
+ * often.
+ */
+ if (a->mtime < b->mtime)
+ return 1;
+ else if (a->mtime == b->mtime)
+ return 0;
+
+ return -1;
+}
+
+
+static int packfile_load__cb(void *data, git_buf *path)
+{
+ struct pack_backend *backend = data;
+ struct git_pack_file *pack;
+ const char *path_str = git_buf_cstr(path);
+ size_t i, cmp_len = git_buf_len(path);
+ int error;
+
+ if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0)
+ return 0; /* not an index */
+
+ cmp_len -= strlen(".idx");
+
+ for (i = 0; i < backend->packs.length; ++i) {
+ struct git_pack_file *p = git_vector_get(&backend->packs, i);
+
+ if (memcmp(p->pack_name, path_str, cmp_len) == 0)
+ return 0;
+ }
+
+ error = git_mwindow_get_pack(&pack, path->ptr);
+
+ /* ignore missing .pack file as git does */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+
+ if (!error)
+ error = git_vector_insert(&backend->packs, pack);
+
+ return error;
+
+}
+
+static int pack_entry_find_inner(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *oid,
+ struct git_pack_file *last_found)
+{
+ size_t i;
+
+ if (last_found &&
+ git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0)
+ return 0;
+
+ for (i = 0; i < backend->packs.length; ++i) {
+ struct git_pack_file *p;
+
+ p = git_vector_get(&backend->packs, i);
+ if (p == last_found)
+ continue;
+
+ if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {
+ backend->last_found = p;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid)
+{
+ struct git_pack_file *last_found = backend->last_found;
+
+ if (backend->last_found &&
+ git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0)
+ return 0;
+
+ if (!pack_entry_find_inner(e, backend, oid, last_found))
+ return 0;
+
+ return git_odb__error_notfound(
+ "failed to find pack entry", oid, GIT_OID_HEXSZ);
+}
+
+static int pack_entry_find_prefix(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ int error;
+ size_t i;
+ git_oid found_full_oid = {{0}};
+ bool found = false;
+ struct git_pack_file *last_found = backend->last_found;
+
+ if (last_found) {
+ error = git_pack_entry_find(e, last_found, short_oid, len);
+ if (error == GIT_EAMBIGUOUS)
+ return error;
+ if (!error) {
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
+ }
+ }
+
+ for (i = 0; i < backend->packs.length; ++i) {
+ struct git_pack_file *p;
+
+ p = git_vector_get(&backend->packs, i);
+ if (p == last_found)
+ continue;
+
+ error = git_pack_entry_find(e, p, short_oid, len);
+ if (error == GIT_EAMBIGUOUS)
+ return error;
+ if (!error) {
+ if (found && git_oid_cmp(&e->sha1, &found_full_oid))
+ return git_odb__error_ambiguous("found multiple pack entries");
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
+ backend->last_found = p;
+ }
+ }
+
+ if (!found)
+ return git_odb__error_notfound("no matching pack entry for prefix",
+ short_oid, len);
+ else
+ return 0;
+}
+
+
+/***********************************************************
+ *
+ * PACKED BACKEND PUBLIC API
+ *
+ * Implement the git_odb_backend API calls
+ *
+ ***********************************************************/
+static int pack_backend__refresh(git_odb_backend *backend_)
+{
+ int error;
+ struct stat st;
+ git_buf path = GIT_BUF_INIT;
+ struct pack_backend *backend = (struct pack_backend *)backend_;
+
+ if (backend->pack_folder == NULL)
+ return 0;
+
+ if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
+ return git_odb__error_notfound("failed to refresh packfiles", NULL, 0);
+
+ git_buf_sets(&path, backend->pack_folder);
+
+ /* reload all packs */
+ error = git_path_direach(&path, 0, packfile_load__cb, backend);
+
+ git_buf_free(&path);
+ git_vector_sort(&backend->packs);
+
+ return error;
+}
+
+static int pack_backend__read_header(
+ size_t *len_p, git_otype *type_p,
+ struct git_odb_backend *backend, const git_oid *oid)
+{
+ struct git_pack_entry e;
+ int error;
+
+ assert(len_p && type_p && backend && oid);
+
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
+ return error;
+
+ return git_packfile_resolve_header(len_p, type_p, e.p, e.offset);
+}
+
+static int pack_backend__freshen(
+ git_odb_backend *backend, const git_oid *oid)
+{
+ struct git_pack_entry e;
+ time_t now;
+ int error;
+
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
+ return error;
+
+ now = time(NULL);
+
+ if (e.p->last_freshen > now - FRESHEN_FREQUENCY)
+ return 0;
+
+ if ((error = git_futils_touch(e.p->pack_name, &now)) < 0)
+ return error;
+
+ e.p->last_freshen = now;
+ return 0;
+}
+
+static int pack_backend__read(
+ void **buffer_p, size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *oid)
+{
+ struct git_pack_entry e;
+ git_rawobj raw = {NULL};
+ int error;
+
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 ||
+ (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0)
+ return error;
+
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+
+ return 0;
+}
+
+static int pack_backend__read_prefix(
+ git_oid *out_oid,
+ void **buffer_p,
+ size_t *len_p,
+ git_otype *type_p,
+ git_odb_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ int error = 0;
+
+ if (len < GIT_OID_MINPREFIXLEN)
+ error = git_odb__error_ambiguous("prefix length too short");
+
+ else if (len >= GIT_OID_HEXSZ) {
+ /* We can fall back to regular read method */
+ error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid);
+ if (!error)
+ git_oid_cpy(out_oid, short_oid);
+ } else {
+ struct git_pack_entry e;
+ git_rawobj raw;
+
+ if ((error = pack_entry_find_prefix(
+ &e, (struct pack_backend *)backend, short_oid, len)) == 0 &&
+ (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0)
+ {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+ git_oid_cpy(out_oid, &e.sha1);
+ }
+ }
+
+ return error;
+}
+
+static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
+{
+ struct git_pack_entry e;
+ return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0;
+}
+
+static int pack_backend__exists_prefix(
+ git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
+{
+ int error;
+ struct pack_backend *pb = (struct pack_backend *)backend;
+ struct git_pack_entry e = {0};
+
+ error = pack_entry_find_prefix(&e, pb, short_id, len);
+ git_oid_cpy(out, &e.sha1);
+ return error;
+}
+
+static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
+{
+ int error;
+ struct git_pack_file *p;
+ struct pack_backend *backend;
+ unsigned int i;
+
+ assert(_backend && cb);
+ backend = (struct pack_backend *)_backend;
+
+ /* Make sure we know about the packfiles */
+ if ((error = pack_backend__refresh(_backend)) < 0)
+ return error;
+
+ git_vector_foreach(&backend->packs, i, p) {
+ if ((error = git_pack_foreach_entry(p, cb, data)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_append(writepack->indexer, data, size, stats);
+}
+
+static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_commit(writepack->indexer, stats);
+}
+
+static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ git_indexer_free(writepack->indexer);
+ git__free(writepack);
+}
+
+static int pack_backend__writepack(struct git_odb_writepack **out,
+ git_odb_backend *_backend,
+ git_odb *odb,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload)
+{
+ struct pack_backend *backend;
+ struct pack_writepack *writepack;
+
+ assert(out && _backend);
+
+ *out = NULL;
+
+ backend = (struct pack_backend *)_backend;
+
+ writepack = git__calloc(1, sizeof(struct pack_writepack));
+ GITERR_CHECK_ALLOC(writepack);
+
+ if (git_indexer_new(&writepack->indexer,
+ backend->pack_folder, 0, odb, progress_cb, progress_payload) < 0) {
+ git__free(writepack);
+ return -1;
+ }
+
+ writepack->parent.backend = _backend;
+ writepack->parent.append = pack_backend__writepack_append;
+ writepack->parent.commit = pack_backend__writepack_commit;
+ writepack->parent.free = pack_backend__writepack_free;
+
+ *out = (git_odb_writepack *)writepack;
+
+ return 0;
+}
+
+static void pack_backend__free(git_odb_backend *_backend)
+{
+ struct pack_backend *backend;
+ size_t i;
+
+ assert(_backend);
+
+ backend = (struct pack_backend *)_backend;
+
+ for (i = 0; i < backend->packs.length; ++i) {
+ struct git_pack_file *p = git_vector_get(&backend->packs, i);
+ git_mwindow_put_pack(p);
+ }
+
+ git_vector_free(&backend->packs);
+ git__free(backend->pack_folder);
+ git__free(backend);
+}
+
+static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
+{
+ struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) {
+ git__free(backend);
+ return -1;
+ }
+
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
+
+ backend->parent.read = &pack_backend__read;
+ backend->parent.read_prefix = &pack_backend__read_prefix;
+ backend->parent.read_header = &pack_backend__read_header;
+ backend->parent.exists = &pack_backend__exists;
+ backend->parent.exists_prefix = &pack_backend__exists_prefix;
+ backend->parent.refresh = &pack_backend__refresh;
+ backend->parent.foreach = &pack_backend__foreach;
+ backend->parent.writepack = &pack_backend__writepack;
+ backend->parent.freshen = &pack_backend__freshen;
+ backend->parent.free = &pack_backend__free;
+
+ *out = backend;
+ return 0;
+}
+
+int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
+{
+ struct pack_backend *backend = NULL;
+ struct git_pack_file *packfile = NULL;
+
+ if (pack_backend__alloc(&backend, 1) < 0)
+ return -1;
+
+ if (git_mwindow_get_pack(&packfile, idx) < 0 ||
+ git_vector_insert(&backend->packs, packfile) < 0)
+ {
+ pack_backend__free((git_odb_backend *)backend);
+ return -1;
+ }
+
+ *backend_out = (git_odb_backend *)backend;
+ return 0;
+}
+
+int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
+{
+ int error = 0;
+ struct pack_backend *backend = NULL;
+ git_buf path = GIT_BUF_INIT;
+
+ if (pack_backend__alloc(&backend, 8) < 0)
+ return -1;
+
+ if (!(error = git_buf_joinpath(&path, objects_dir, "pack")) &&
+ git_path_isdir(git_buf_cstr(&path)))
+ {
+ backend->pack_folder = git_buf_detach(&path);
+ error = pack_backend__refresh((git_odb_backend *)backend);
+ }
+
+ if (error < 0) {
+ pack_backend__free((git_odb_backend *)backend);
+ backend = NULL;
+ }
+
+ *backend_out = (git_odb_backend *)backend;
+
+ git_buf_free(&path);
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) 2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_offmap_h__
+#define INCLUDE_offmap_h__
+
+#include "common.h"
+#include "git2/types.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kreallocarray git__reallocarray
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(off, git_off_t, void *)
+typedef khash_t(off) git_offmap;
+
+#define GIT__USE_OFFMAP \
+ __KHASH_IMPL(off, static kh_inline, git_off_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+#define git_offmap_alloc() kh_init(off)
+#define git_offmap_free(h) kh_destroy(off, h), h = NULL
+#define git_offmap_clear(h) kh_clear(off, h)
+
+#define git_offmap_num_entries(h) kh_size(h)
+
+#define git_offmap_lookup_index(h, k) kh_get(off, h, k)
+#define git_offmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_offmap_exists(h, k) (kh_get(off, h, k) != kh_end(h))
+
+#define git_offmap_value_at(h, idx) kh_val(h, idx)
+#define git_offmap_set_value_at(h, idx, v) kh_val(h, idx) = v
+#define git_offmap_delete_at(h, idx) kh_del(off, h, idx)
+
+#define git_offmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_insert2(h, key, val, oldv, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) { \
+ oldv = kh_val(h, __pos); \
+ kh_key(h, __pos) = key; \
+ } else { oldv = NULL; } \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_delete(h, key) do { \
+ khiter_t __pos = git_offmap_lookup_index(h, key); \
+ if (git_offmap_valid_index(h, __pos)) \
+ git_offmap_delete_at(h, __pos); } while (0)
+
+#define git_offmap_foreach kh_foreach
+#define git_offmap_foreach_value kh_foreach_value
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/oid.h"
+#include "repository.h"
+#include "global.h"
+#include <string.h>
+#include <limits.h>
+
+static char to_hex[] = "0123456789abcdef";
+
+static int oid_error_invalid(const char *msg)
+{
+ giterr_set(GITERR_INVALID, "Unable to parse OID - %s", msg);
+ return -1;
+}
+
+int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
+{
+ size_t p;
+ int v;
+
+ assert(out && str);
+
+ if (!length)
+ return oid_error_invalid("too short");
+
+ if (length > GIT_OID_HEXSZ)
+ return oid_error_invalid("too long");
+
+ memset(out->id, 0, GIT_OID_RAWSZ);
+
+ for (p = 0; p < length; p++) {
+ v = git__fromhex(str[p]);
+ if (v < 0)
+ return oid_error_invalid("contains invalid characters");
+
+ out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4));
+ }
+
+ return 0;
+}
+
+int git_oid_fromstrp(git_oid *out, const char *str)
+{
+ return git_oid_fromstrn(out, str, strlen(str));
+}
+
+int git_oid_fromstr(git_oid *out, const char *str)
+{
+ return git_oid_fromstrn(out, str, GIT_OID_HEXSZ);
+}
+
+GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
+{
+ *str++ = to_hex[val >> 4];
+ *str++ = to_hex[val & 0xf];
+ return str;
+}
+
+void git_oid_nfmt(char *str, size_t n, const git_oid *oid)
+{
+ size_t i, max_i;
+
+ if (!oid) {
+ memset(str, 0, n);
+ return;
+ }
+ if (n > GIT_OID_HEXSZ) {
+ memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
+ n = GIT_OID_HEXSZ;
+ }
+
+ max_i = n / 2;
+
+ for (i = 0; i < max_i; i++)
+ str = fmt_one(str, oid->id[i]);
+
+ if (n & 1)
+ *str++ = to_hex[oid->id[i] >> 4];
+}
+
+void git_oid_fmt(char *str, const git_oid *oid)
+{
+ git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
+}
+
+void git_oid_pathfmt(char *str, const git_oid *oid)
+{
+ size_t i;
+
+ str = fmt_one(str, oid->id[0]);
+ *str++ = '/';
+ for (i = 1; i < sizeof(oid->id); i++)
+ str = fmt_one(str, oid->id[i]);
+}
+
+char *git_oid_tostr_s(const git_oid *oid)
+{
+ char *str = GIT_GLOBAL->oid_fmt;
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
+ return str;
+}
+
+char *git_oid_allocfmt(const git_oid *oid)
+{
+ char *str = git__malloc(GIT_OID_HEXSZ + 1);
+ if (!str)
+ return NULL;
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
+ return str;
+}
+
+char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
+{
+ if (!out || n == 0)
+ return "";
+
+ if (n > GIT_OID_HEXSZ + 1)
+ n = GIT_OID_HEXSZ + 1;
+
+ git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
+ out[n - 1] = '\0';
+
+ return out;
+}
+
+int git_oid__parse(
+ git_oid *oid, const char **buffer_out,
+ const char *buffer_end, const char *header)
+{
+ const size_t sha_len = GIT_OID_HEXSZ;
+ const size_t header_len = strlen(header);
+
+ const char *buffer = *buffer_out;
+
+ if (buffer + (header_len + sha_len + 1) > buffer_end)
+ return -1;
+
+ if (memcmp(buffer, header, header_len) != 0)
+ return -1;
+
+ if (buffer[header_len + sha_len] != '\n')
+ return -1;
+
+ if (git_oid_fromstr(oid, buffer + header_len) < 0)
+ return -1;
+
+ *buffer_out = buffer + (header_len + sha_len + 1);
+
+ return 0;
+}
+
+void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid)
+{
+ char hex_oid[GIT_OID_HEXSZ];
+
+ git_oid_fmt(hex_oid, oid);
+ git_buf_puts(buf, header);
+ git_buf_put(buf, hex_oid, GIT_OID_HEXSZ);
+ git_buf_putc(buf, '\n');
+}
+
+void git_oid_fromraw(git_oid *out, const unsigned char *raw)
+{
+ memcpy(out->id, raw, sizeof(out->id));
+}
+
+void git_oid_cpy(git_oid *out, const git_oid *src)
+{
+ memcpy(out->id, src->id, sizeof(out->id));
+}
+
+int git_oid_cmp(const git_oid *a, const git_oid *b)
+{
+ return git_oid__cmp(a, b);
+}
+
+int git_oid_equal(const git_oid *a, const git_oid *b)
+{
+ return (git_oid__cmp(a, b) == 0);
+}
+
+int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len)
+{
+ const unsigned char *a = oid_a->id;
+ const unsigned char *b = oid_b->id;
+
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ while (len > 1) {
+ if (*a != *b)
+ return 1;
+ a++;
+ b++;
+ len -= 2;
+ };
+
+ if (len)
+ if ((*a ^ *b) & 0xf0)
+ return 1;
+
+ return 0;
+}
+
+int git_oid_strcmp(const git_oid *oid_a, const char *str)
+{
+ const unsigned char *a;
+ unsigned char strval;
+ int hexval;
+
+ for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval = (unsigned char)(hexval << 4);
+ if (*str) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval |= hexval;
+ }
+ if (*a != strval)
+ return (*a - strval);
+ }
+
+ return 0;
+}
+
+int git_oid_streq(const git_oid *oid_a, const char *str)
+{
+ return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1;
+}
+
+int git_oid_iszero(const git_oid *oid_a)
+{
+ const unsigned char *a = oid_a->id;
+ unsigned int i;
+ for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a)
+ if (*a != 0)
+ return 0;
+ return 1;
+}
+
+typedef short node_index;
+
+typedef union {
+ const char *tail;
+ node_index children[16];
+} trie_node;
+
+struct git_oid_shorten {
+ trie_node *nodes;
+ size_t node_count, size;
+ int min_length, full;
+};
+
+static int resize_trie(git_oid_shorten *self, size_t new_size)
+{
+ self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node));
+ GITERR_CHECK_ALLOC(self->nodes);
+
+ if (new_size > self->size) {
+ memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node));
+ }
+
+ self->size = new_size;
+ return 0;
+}
+
+static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid)
+{
+ trie_node *node, *leaf;
+ node_index idx_leaf;
+
+ if (os->node_count >= os->size) {
+ if (resize_trie(os, os->size * 2) < 0)
+ return NULL;
+ }
+
+ idx_leaf = (node_index)os->node_count++;
+
+ if (os->node_count == SHRT_MAX) {
+ os->full = 1;
+ return NULL;
+ }
+
+ node = &os->nodes[idx];
+ node->children[push_at] = -idx_leaf;
+
+ leaf = &os->nodes[idx_leaf];
+ leaf->tail = oid;
+
+ return node;
+}
+
+git_oid_shorten *git_oid_shorten_new(size_t min_length)
+{
+ git_oid_shorten *os;
+
+ assert((size_t)((int)min_length) == min_length);
+
+ os = git__calloc(1, sizeof(git_oid_shorten));
+ if (os == NULL)
+ return NULL;
+
+ if (resize_trie(os, 16) < 0) {
+ git__free(os);
+ return NULL;
+ }
+
+ os->node_count = 1;
+ os->min_length = (int)min_length;
+
+ return os;
+}
+
+void git_oid_shorten_free(git_oid_shorten *os)
+{
+ if (os == NULL)
+ return;
+
+ git__free(os->nodes);
+ git__free(os);
+}
+
+
+/*
+ * What wizardry is this?
+ *
+ * This is just a memory-optimized trie: basically a very fancy
+ * 16-ary tree, which is used to store the prefixes of the OID
+ * strings.
+ *
+ * Read more: http://en.wikipedia.org/wiki/Trie
+ *
+ * Magic that happens in this method:
+ *
+ * - Each node in the trie is an union, so it can work both as
+ * a normal node, or as a leaf.
+ *
+ * - Each normal node points to 16 children (one for each possible
+ * character in the oid). This is *not* stored in an array of
+ * pointers, because in a 64-bit arch this would be sucking
+ * 16*sizeof(void*) = 128 bytes of memory per node, which is
+ * insane. What we do is store Node Indexes, and use these indexes
+ * to look up each node in the om->index array. These indexes are
+ * signed shorts, so this limits the amount of unique OIDs that
+ * fit in the structure to about 20000 (assuming a more or less uniform
+ * distribution).
+ *
+ * - All the nodes in om->index array are stored contiguously in
+ * memory, and each of them is 32 bytes, so we fit 2x nodes per
+ * cache line. Convenient for speed.
+ *
+ * - To differentiate the leafs from the normal nodes, we store all
+ * the indexes towards a leaf as a negative index (indexes to normal
+ * nodes are positives). When we find that one of the children for
+ * a node has a negative value, that means it's going to be a leaf.
+ * This reduces the amount of indexes we have by two, but also reduces
+ * the size of each node by 1-4 bytes (the amount we would need to
+ * add a `is_leaf` field): this is good because it allows the nodes
+ * to fit cleanly in cache lines.
+ *
+ * - Once we reach an empty children, instead of continuing to insert
+ * new nodes for each remaining character of the OID, we store a pointer
+ * to the tail in the leaf; if the leaf is reached again, we turn it
+ * into a normal node and use the tail to create a new leaf.
+ *
+ * This is a pretty good balance between performance and memory usage.
+ */
+int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid)
+{
+ int i;
+ bool is_leaf;
+ node_index idx;
+
+ if (os->full) {
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
+
+ if (text_oid == NULL)
+ return os->min_length;
+
+ idx = 0;
+ is_leaf = false;
+
+ for (i = 0; i < GIT_OID_HEXSZ; ++i) {
+ int c = git__fromhex(text_oid[i]);
+ trie_node *node;
+
+ if (c == -1) {
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - invalid hex value");
+ return -1;
+ }
+
+ node = &os->nodes[idx];
+
+ if (is_leaf) {
+ const char *tail;
+
+ tail = node->tail;
+ node->tail = NULL;
+
+ node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]);
+ if (node == NULL) {
+ if (os->full)
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
+ }
+
+ if (node->children[c] == 0) {
+ if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) {
+ if (os->full)
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
+ break;
+ }
+
+ idx = node->children[c];
+ is_leaf = false;
+
+ if (idx < 0) {
+ node->children[c] = idx = -idx;
+ is_leaf = true;
+ }
+ }
+
+ if (++i > os->min_length)
+ os->min_length = i;
+
+ return os->min_length;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_oid_h__
+#define INCLUDE_oid_h__
+
+#include "git2/oid.h"
+
+/**
+ * Format a git_oid into a newly allocated c-string.
+ *
+ * The c-string is owned by the caller and needs to be manually freed.
+ *
+ * @param id the oid structure to format
+ * @return the c-string; NULL if memory is exhausted. Caller must
+ * deallocate the string with git__free().
+ */
+char *git_oid_allocfmt(const git_oid *id);
+
+GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+{
+ int i;
+
+ for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) {
+ if (*sha1 != *sha2)
+ return *sha1 - *sha2;
+ }
+
+ return 0;
+}
+
+/*
+ * Compare two oid structures.
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return <0, 0, >0 if a < b, a == b, a > b.
+ */
+GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b)
+{
+ return git_oid__hashcmp(a->id, b->id);
+}
+
+GIT_INLINE(void) git_oid__cpy_prefix(
+ git_oid *out, const git_oid *id, size_t len)
+{
+ memcpy(&out->id, id->id, (len + 1) / 2);
+
+ if (len & 1)
+ out->id[len / 2] &= 0xF0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/oidarray.h"
+#include "oidarray.h"
+#include "array.h"
+
+void git_oidarray_free(git_oidarray *arr)
+{
+ git__free(arr->ids);
+}
+
+void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array)
+{
+ arr->count = array->size;
+ arr->ids = array->ptr;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_oidarray_h__
+#define INCLUDE_oidarray_h__
+
+#include "common.h"
+#include "git2/oidarray.h"
+#include "array.h"
+
+typedef git_array_t(git_oid) git_array_oid_t;
+
+extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_oidmap_h__
+#define INCLUDE_oidmap_h__
+
+#include "common.h"
+#include "git2/oid.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kreallocarray git__reallocarray
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(oid, const git_oid *, void *)
+typedef khash_t(oid) git_oidmap;
+
+GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid)
+{
+ khint_t h;
+ memcpy(&h, oid, sizeof(khint_t));
+ return h;
+}
+
+#define GIT__USE_OIDMAP \
+ __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal)
+
+#define git_oidmap_alloc() kh_init(oid)
+#define git_oidmap_free(h) kh_destroy(oid,h), h = NULL
+
+#define git_oidmap_lookup_index(h, k) kh_get(oid, h, k)
+#define git_oidmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_oidmap_value_at(h, idx) kh_val(h, idx)
+
+#define git_oidmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(oid, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_oidmap_foreach_value kh_foreach_value
+
+#define git_oidmap_size(h) kh_size(h)
+
+#define git_oidmap_clear(h) kh_clear(oid, h)
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_OPENSSL
+
+#include <ctype.h>
+
+#include "global.h"
+#include "posix.h"
+#include "stream.h"
+#include "socket_stream.h"
+#include "openssl_stream.h"
+#include "netops.h"
+#include "git2/transport.h"
+#include "git2/sys/openssl.h"
+
+#ifdef GIT_CURL
+# include "curl_stream.h"
+#endif
+
+#ifndef GIT_WIN32
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+#include <openssl/bio.h>
+
+SSL_CTX *git__ssl_ctx;
+
+#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA"
+
+#ifdef GIT_THREADS
+
+static git_mutex *openssl_locks;
+
+static void openssl_locking_function(
+ int mode, int n, const char *file, int line)
+{
+ int lock;
+
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+ lock = mode & CRYPTO_LOCK;
+
+ if (lock) {
+ git_mutex_lock(&openssl_locks[n]);
+ } else {
+ git_mutex_unlock(&openssl_locks[n]);
+ }
+}
+
+static void shutdown_ssl_locking(void)
+{
+ int num_locks, i;
+
+ num_locks = CRYPTO_num_locks();
+ CRYPTO_set_locking_callback(NULL);
+
+ for (i = 0; i < num_locks; ++i)
+ git_mutex_free(openssl_locks);
+ git__free(openssl_locks);
+}
+
+#endif /* GIT_THREADS */
+
+static BIO_METHOD *git_stream_bio_method;
+static int init_bio_method(void);
+
+/**
+ * This function aims to clean-up the SSL context which
+ * we allocated.
+ */
+static void shutdown_ssl(void)
+{
+ if (git_stream_bio_method) {
+ BIO_meth_free(git_stream_bio_method);
+ git_stream_bio_method = NULL;
+ }
+
+ if (git__ssl_ctx) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ }
+}
+
+int git_openssl_stream_global_init(void)
+{
+#ifdef GIT_OPENSSL
+ long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ const char *ciphers = git_libgit2__ssl_ciphers();
+
+ /* Older OpenSSL and MacOS OpenSSL doesn't have this */
+#ifdef SSL_OP_NO_COMPRESSION
+ ssl_opts |= SSL_OP_NO_COMPRESSION;
+#endif
+
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+ /*
+ * Load SSLv{2,3} and TLSv1 so that we can talk with servers
+ * which use the SSL hellos, which are often used for
+ * compatibility. We then disable SSL so we only allow OpenSSL
+ * to speak TLSv1 to perform the encryption itself.
+ */
+ git__ssl_ctx = SSL_CTX_new(SSLv23_method());
+ SSL_CTX_set_options(git__ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ return -1;
+ }
+
+ if (!ciphers) {
+ ciphers = GIT_SSL_DEFAULT_CIPHERS;
+ }
+
+ if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ return -1;
+ }
+
+ if (init_bio_method() < 0) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ return -1;
+ }
+
+#endif
+
+ git__on_shutdown(shutdown_ssl);
+
+ return 0;
+}
+
+int git_openssl_set_locking(void)
+{
+#ifdef GIT_THREADS
+ int num_locks, i;
+
+ num_locks = CRYPTO_num_locks();
+ openssl_locks = git__calloc(num_locks, sizeof(git_mutex));
+ GITERR_CHECK_ALLOC(openssl_locks);
+
+ for (i = 0; i < num_locks; i++) {
+ if (git_mutex_init(&openssl_locks[i]) != 0) {
+ giterr_set(GITERR_SSL, "failed to initialize openssl locks");
+ return -1;
+ }
+ }
+
+ CRYPTO_set_locking_callback(openssl_locking_function);
+ git__on_shutdown(shutdown_ssl_locking);
+ return 0;
+#else
+ giterr_set(GITERR_THREAD, "libgit2 was not built with threads");
+ return -1;
+#endif
+}
+
+
+static int bio_create(BIO *b)
+{
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+
+ return 1;
+}
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+ BIO_set_data(b, NULL);
+
+ return 1;
+}
+
+static int bio_read(BIO *b, char *buf, int len)
+{
+ git_stream *io = (git_stream *) BIO_get_data(b);
+
+ return (int) git_stream_read(io, buf, len);
+}
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+ git_stream *io = (git_stream *) BIO_get_data(b);
+
+ return (int) git_stream_write(io, buf, len, 0);
+}
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(num);
+ GIT_UNUSED(ptr);
+
+ if (cmd == BIO_CTRL_FLUSH)
+ return 1;
+
+ return 0;
+}
+
+static int bio_gets(BIO *b, char *buf, int len)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(buf);
+ GIT_UNUSED(len);
+ return -1;
+}
+
+static int bio_puts(BIO *b, const char *str)
+{
+ return bio_write(b, str, strlen(str));
+}
+
+static int init_bio_method(void)
+{
+ /* Set up the BIO_METHOD we use for wrapping our own stream implementations */
+ git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream");
+ GITERR_CHECK_ALLOC(git_stream_bio_method);
+
+ BIO_meth_set_write(git_stream_bio_method, bio_write);
+ BIO_meth_set_read(git_stream_bio_method, bio_read);
+ BIO_meth_set_puts(git_stream_bio_method, bio_puts);
+ BIO_meth_set_gets(git_stream_bio_method, bio_gets);
+ BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl);
+ BIO_meth_set_create(git_stream_bio_method, bio_create);
+ BIO_meth_set_destroy(git_stream_bio_method, bio_destroy);
+
+ return 0;
+}
+
+static int ssl_set_error(SSL *ssl, int error)
+{
+ int err;
+ unsigned long e;
+
+ err = SSL_get_error(ssl, error);
+
+ assert(err != SSL_ERROR_WANT_READ);
+ assert(err != SSL_ERROR_WANT_WRITE);
+
+ switch (err) {
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ giterr_set(GITERR_NET, "SSL error: connection failure\n");
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ giterr_set(GITERR_NET, "SSL error: x509 error\n");
+ break;
+ case SSL_ERROR_SYSCALL:
+ e = ERR_get_error();
+ if (e > 0) {
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ } else if (error < 0) {
+ giterr_set(GITERR_OS, "SSL error: syscall failure");
+ break;
+ }
+ giterr_set(GITERR_NET, "SSL error: received early EOF");
+ return GIT_EEOF;
+ break;
+ case SSL_ERROR_SSL:
+ e = ERR_get_error();
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ giterr_set(GITERR_NET, "SSL error: unknown error");
+ break;
+ }
+ return -1;
+}
+
+static int ssl_teardown(SSL *ssl)
+{
+ int ret;
+
+ ret = SSL_shutdown(ssl);
+ if (ret < 0)
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
+
+ return ret;
+}
+
+static int check_host_name(const char *name, const char *host)
+{
+ if (!strcasecmp(name, host))
+ return 0;
+
+ if (gitno__match_host(name, host) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int verify_server_cert(SSL *ssl, const char *host)
+{
+ X509 *cert;
+ X509_NAME *peer_name;
+ ASN1_STRING *str;
+ unsigned char *peer_cn = NULL;
+ int matched = -1, type = GEN_DNS;
+ GENERAL_NAMES *alts;
+ struct in6_addr addr6;
+ struct in_addr addr4;
+ void *addr;
+ int i = -1,j;
+
+ if (SSL_get_verify_result(ssl) != X509_V_OK) {
+ giterr_set(GITERR_SSL, "The SSL certificate is invalid");
+ return GIT_ECERTIFICATE;
+ }
+
+ /* Try to parse the host as an IP address to see if it is */
+ if (p_inet_pton(AF_INET, host, &addr4)) {
+ type = GEN_IPADD;
+ addr = &addr4;
+ } else {
+ if(p_inet_pton(AF_INET6, host, &addr6)) {
+ type = GEN_IPADD;
+ addr = &addr6;
+ }
+ }
+
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert) {
+ giterr_set(GITERR_SSL, "the server did not provide a certificate");
+ return -1;
+ }
+
+ /* Check the alternative names */
+ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (alts) {
+ int num;
+
+ num = sk_GENERAL_NAME_num(alts);
+ for (i = 0; i < num && matched != 1; i++) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
+ const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5);
+ size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
+
+ /* Skip any names of a type we're not looking for */
+ if (gn->type != type)
+ continue;
+
+ if (type == GEN_DNS) {
+ /* If it contains embedded NULs, don't even try */
+ if (memchr(name, '\0', namelen))
+ continue;
+
+ if (check_host_name(name, host) < 0)
+ matched = 0;
+ else
+ matched = 1;
+ } else if (type == GEN_IPADD) {
+ /* Here name isn't so much a name but a binary representation of the IP */
+ matched = !!memcmp(name, addr, namelen);
+ }
+ }
+ }
+ GENERAL_NAMES_free(alts);
+
+ if (matched == 0)
+ goto cert_fail_name;
+
+ if (matched == 1)
+ return 0;
+
+ /* If no alternative names are available, check the common name */
+ peer_name = X509_get_subject_name(cert);
+ if (peer_name == NULL)
+ goto on_error;
+
+ if (peer_name) {
+ /* Get the index of the last CN entry */
+ while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
+ i = j;
+ }
+
+ if (i < 0)
+ goto on_error;
+
+ str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
+ if (str == NULL)
+ goto on_error;
+
+ /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
+ if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
+ int size = ASN1_STRING_length(str);
+
+ if (size > 0) {
+ peer_cn = OPENSSL_malloc(size + 1);
+ GITERR_CHECK_ALLOC(peer_cn);
+ memcpy(peer_cn, ASN1_STRING_get0_data(str), size);
+ peer_cn[size] = '\0';
+ } else {
+ goto cert_fail_name;
+ }
+ } else {
+ int size = ASN1_STRING_to_UTF8(&peer_cn, str);
+ GITERR_CHECK_ALLOC(peer_cn);
+ if (memchr(peer_cn, '\0', size))
+ goto cert_fail_name;
+ }
+
+ if (check_host_name((char *)peer_cn, host) < 0)
+ goto cert_fail_name;
+
+ OPENSSL_free(peer_cn);
+
+ return 0;
+
+on_error:
+ OPENSSL_free(peer_cn);
+ return ssl_set_error(ssl, 0);
+
+cert_fail_name:
+ OPENSSL_free(peer_cn);
+ giterr_set(GITERR_SSL, "hostname does not match certificate");
+ return GIT_ECERTIFICATE;
+}
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ bool connected;
+ char *host;
+ SSL *ssl;
+ git_cert_x509 cert_info;
+} openssl_stream;
+
+int openssl_close(git_stream *stream);
+
+int openssl_connect(git_stream *stream)
+{
+ int ret;
+ BIO *bio;
+ openssl_stream *st = (openssl_stream *) stream;
+
+ if ((ret = git_stream_connect(st->io)) < 0)
+ return ret;
+
+ st->connected = true;
+
+ bio = BIO_new(git_stream_bio_method);
+ GITERR_CHECK_ALLOC(bio);
+
+ BIO_set_data(bio, st->io);
+ SSL_set_bio(st->ssl, bio, bio);
+
+ /* specify the host in case SNI is needed */
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ SSL_set_tlsext_host_name(st->ssl, st->host);
+#endif
+
+ if ((ret = SSL_connect(st->ssl)) <= 0)
+ return ssl_set_error(st->ssl, ret);
+
+ return verify_server_cert(st->ssl, st->host);
+}
+
+int openssl_certificate(git_cert **out, git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int len;
+ X509 *cert = SSL_get_peer_certificate(st->ssl);
+ unsigned char *guard, *encoded_cert;
+
+ /* Retrieve the length of the certificate first */
+ len = i2d_X509(cert, NULL);
+ if (len < 0) {
+ giterr_set(GITERR_NET, "failed to retrieve certificate information");
+ return -1;
+ }
+
+ encoded_cert = git__malloc(len);
+ GITERR_CHECK_ALLOC(encoded_cert);
+ /* i2d_X509 makes 'guard' point to just after the data */
+ guard = encoded_cert;
+
+ len = i2d_X509(cert, &guard);
+ if (len < 0) {
+ git__free(encoded_cert);
+ giterr_set(GITERR_NET, "failed to retrieve certificate information");
+ return -1;
+ }
+
+ st->cert_info.parent.cert_type = GIT_CERT_X509;
+ st->cert_info.data = encoded_cert;
+ st->cert_info.len = len;
+
+ *out = &st->cert_info.parent;
+
+ return 0;
+}
+
+static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_opts);
+}
+
+ssize_t openssl_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret;
+
+ GIT_UNUSED(flags);
+
+ if ((ret = SSL_write(st->ssl, data, len)) <= 0) {
+ return ssl_set_error(st->ssl, ret);
+ }
+
+ return ret;
+}
+
+ssize_t openssl_read(git_stream *stream, void *data, size_t len)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret;
+
+ if ((ret = SSL_read(st->ssl, data, len)) <= 0)
+ return ssl_set_error(st->ssl, ret);
+
+ return ret;
+}
+
+int openssl_close(git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret;
+
+ if (st->connected && (ret = ssl_teardown(st->ssl)) < 0)
+ return -1;
+
+ st->connected = false;
+
+ return git_stream_close(st->io);
+}
+
+void openssl_free(git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+
+ SSL_free(st->ssl);
+ git__free(st->host);
+ git__free(st->cert_info.data);
+ git_stream_free(st->io);
+ git__free(st);
+}
+
+int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ int error;
+ openssl_stream *st;
+
+ st = git__calloc(1, sizeof(openssl_stream));
+ GITERR_CHECK_ALLOC(st);
+
+ st->io = NULL;
+#ifdef GIT_CURL
+ error = git_curl_stream_new(&st->io, host, port);
+#else
+ error = git_socket_stream_new(&st->io, host, port);
+#endif
+
+ if (error < 0)
+ goto out_err;
+
+ st->ssl = SSL_new(git__ssl_ctx);
+ if (st->ssl == NULL) {
+ giterr_set(GITERR_SSL, "failed to create ssl object");
+ error = -1;
+ goto out_err;
+ }
+
+ st->host = git__strdup(host);
+ GITERR_CHECK_ALLOC(st->host);
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
+ st->parent.connect = openssl_connect;
+ st->parent.certificate = openssl_certificate;
+ st->parent.set_proxy = openssl_set_proxy;
+ st->parent.read = openssl_read;
+ st->parent.write = openssl_write;
+ st->parent.close = openssl_close;
+ st->parent.free = openssl_free;
+
+ *out = (git_stream *) st;
+ return 0;
+
+out_err:
+ git_stream_free(st->io);
+ git__free(st);
+
+ return error;
+}
+
+#else
+
+#include "stream.h"
+#include "git2/sys/openssl.h"
+
+int git_openssl_stream_global_init(void)
+{
+ return 0;
+}
+
+int git_openssl_set_locking(void)
+{
+ giterr_set(GITERR_SSL, "libgit2 was not built with OpenSSL support");
+ return -1;
+}
+
+int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(host);
+ GIT_UNUSED(port);
+
+ giterr_set(GITERR_SSL, "openssl is not supported in this version");
+ return -1;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_openssl_stream_h__
+#define INCLUDE_openssl_stream_h__
+
+#include "git2/sys/stream.h"
+
+extern int git_openssl_stream_global_init(void);
+
+extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
+
+/*
+ * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it
+ * which do not exist in previous versions. We define these inline functions so
+ * we can program against the interface instead of littering the implementation
+ * with ifdefs.
+ */
+#ifdef GIT_OPENSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+# include <openssl/bio.h>
+
+
+
+# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+
+GIT_INLINE(BIO_METHOD*) BIO_meth_new(int type, const char *name)
+{
+ BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD));
+ if (!meth) {
+ return NULL;
+ }
+
+ meth->type = type;
+ meth->name = name;
+
+ return meth;
+}
+
+GIT_INLINE(void) BIO_meth_free(BIO_METHOD *biom)
+{
+ git__free(biom);
+}
+
+GIT_INLINE(int) BIO_meth_set_write(BIO_METHOD *biom, int (*write) (BIO *, const char *, int))
+{
+ biom->bwrite = write;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_read(BIO_METHOD *biom, int (*read) (BIO *, char *, int))
+{
+ biom->bread = read;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_puts(BIO_METHOD *biom, int (*puts) (BIO *, const char *))
+{
+ biom->bputs = puts;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_gets(BIO_METHOD *biom, int (*gets) (BIO *, char *, int))
+
+{
+ biom->bgets = gets;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_ctrl(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *))
+{
+ biom->ctrl = ctrl;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_create(BIO_METHOD *biom, int (*create) (BIO *))
+{
+ biom->create = create;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_meth_set_destroy(BIO_METHOD *biom, int (*destroy) (BIO *))
+{
+ biom->destroy = destroy;
+ return 1;
+}
+
+GIT_INLINE(int) BIO_get_new_index(void)
+{
+ /* This exists as of 1.1 so before we'd just have 0 */
+ return 0;
+}
+
+GIT_INLINE(void) BIO_set_init(BIO *b, int init)
+{
+ b->init = init;
+}
+
+GIT_INLINE(void) BIO_set_data(BIO *a, void *ptr)
+{
+ a->ptr = ptr;
+}
+
+GIT_INLINE(void*) BIO_get_data(BIO *a)
+{
+ return a->ptr;
+}
+
+GIT_INLINE(const unsigned char *) ASN1_STRING_get0_data(const ASN1_STRING *x)
+{
+ return ASN1_STRING_data((ASN1_STRING *)x);
+}
+
+# endif // OpenSSL < 1.1
+#endif // GIT_OPENSSL
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "pack-objects.h"
+
+#include "zstream.h"
+#include "delta.h"
+#include "iterator.h"
+#include "netops.h"
+#include "pack.h"
+#include "thread-utils.h"
+#include "tree.h"
+#include "util.h"
+#include "revwalk.h"
+#include "commit_list.h"
+
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/tag.h"
+#include "git2/indexer.h"
+#include "git2/config.h"
+
+struct unpacked {
+ git_pobject *object;
+ void *data;
+ struct git_delta_index *index;
+ size_t depth;
+};
+
+struct tree_walk_context {
+ git_packbuilder *pb;
+ git_buf buf;
+};
+
+struct pack_write_context {
+ git_indexer *indexer;
+ git_transfer_progress *stats;
+};
+
+GIT__USE_OIDMAP
+
+#ifdef GIT_THREADS
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) do { \
+ int result = git_mutex_##op(&(pb)->mtx); \
+ assert(!result); \
+ GIT_UNUSED(result); \
+ } while (0)
+
+#else
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb,mtx,op) GIT_UNUSED(pb)
+
+#endif /* GIT_THREADS */
+
+#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock)
+#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock)
+#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock)
+#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock)
+
+/* The minimal interval between progress updates (in seconds). */
+#define MIN_PROGRESS_UPDATE_INTERVAL 0.5
+
+/* Size of the buffer to feed to zlib */
+#define COMPRESS_BUFLEN (1024 * 1024)
+
+static unsigned name_hash(const char *name)
+{
+ unsigned c, hash = 0;
+
+ if (!name)
+ return 0;
+
+ /*
+ * This effectively just creates a sortable number from the
+ * last sixteen non-whitespace characters. Last characters
+ * count "most", so things that end in ".c" sort together.
+ */
+ while ((c = *name++) != 0) {
+ if (git__isspace(c))
+ continue;
+ hash = (hash >> 2) + (c << 24);
+ }
+ return hash;
+}
+
+static int packbuilder_config(git_packbuilder *pb)
+{
+ git_config *config;
+ int ret = 0;
+ int64_t val;
+
+ if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0)
+ return ret;
+
+#define config_get(KEY,DST,DFLT) do { \
+ ret = git_config_get_int64(&val, config, KEY); \
+ if (!ret) { \
+ if (!git__is_sizet(val)) { \
+ giterr_set(GITERR_CONFIG, \
+ "configuration value '%s' is too large", KEY); \
+ ret = -1; \
+ goto out; \
+ } \
+ (DST) = (size_t)val; \
+ } else if (ret == GIT_ENOTFOUND) { \
+ (DST) = (DFLT); \
+ ret = 0; \
+ } else if (ret < 0) goto out; } while (0)
+
+ config_get("pack.deltaCacheSize", pb->max_delta_cache_size,
+ GIT_PACK_DELTA_CACHE_SIZE);
+ config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size,
+ GIT_PACK_DELTA_CACHE_LIMIT);
+ config_get("pack.deltaCacheSize", pb->big_file_threshold,
+ GIT_PACK_BIG_FILE_THRESHOLD);
+ config_get("pack.windowMemory", pb->window_memory_limit, 0);
+
+#undef config_get
+
+out:
+ git_config_free(config);
+
+ return ret;
+}
+
+int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
+{
+ git_packbuilder *pb;
+
+ *out = NULL;
+
+ pb = git__calloc(1, sizeof(*pb));
+ GITERR_CHECK_ALLOC(pb);
+
+ pb->object_ix = git_oidmap_alloc();
+ if (!pb->object_ix)
+ goto on_error;
+
+ pb->walk_objects = git_oidmap_alloc();
+ if (!pb->walk_objects)
+ goto on_error;
+
+ git_pool_init(&pb->object_pool, sizeof(git_walk_object));
+
+ pb->repo = repo;
+ pb->nr_threads = 1; /* do not spawn any thread by default */
+
+ if (git_hash_ctx_init(&pb->ctx) < 0 ||
+ git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 ||
+ git_repository_odb(&pb->odb, repo) < 0 ||
+ packbuilder_config(pb) < 0)
+ goto on_error;
+
+#ifdef GIT_THREADS
+
+ if (git_mutex_init(&pb->cache_mutex) ||
+ git_mutex_init(&pb->progress_mutex) ||
+ git_cond_init(&pb->progress_cond))
+ {
+ giterr_set(GITERR_OS, "Failed to initialize packbuilder mutex");
+ goto on_error;
+ }
+
+#endif
+
+ *out = pb;
+ return 0;
+
+on_error:
+ git_packbuilder_free(pb);
+ return -1;
+}
+
+unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n)
+{
+ assert(pb);
+
+#ifdef GIT_THREADS
+ pb->nr_threads = n;
+#else
+ GIT_UNUSED(n);
+ assert(1 == pb->nr_threads);
+#endif
+
+ return pb->nr_threads;
+}
+
+static void rehash(git_packbuilder *pb)
+{
+ git_pobject *po;
+ khiter_t pos;
+ size_t i;
+ int ret;
+
+ kh_clear(oid, pb->object_ix);
+ for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) {
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ kh_value(pb->object_ix, pos) = po;
+ }
+}
+
+int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid,
+ const char *name)
+{
+ git_pobject *po;
+ khiter_t pos;
+ size_t newsize;
+ int ret;
+
+ assert(pb && oid);
+
+ /* If the object already exists in the hash table, then we don't
+ * have any work to do */
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos != kh_end(pb->object_ix))
+ return 0;
+
+ if (pb->nr_objects >= pb->nr_alloc) {
+ GITERR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024);
+ GITERR_CHECK_ALLOC_MULTIPLY(&newsize, newsize, 3 / 2);
+
+ if (!git__is_uint32(newsize)) {
+ giterr_set(GITERR_NOMEMORY, "Packfile too large to fit in memory.");
+ return -1;
+ }
+
+ pb->nr_alloc = newsize;
+
+ pb->object_list = git__reallocarray(pb->object_list,
+ pb->nr_alloc, sizeof(*po));
+ GITERR_CHECK_ALLOC(pb->object_list);
+ rehash(pb);
+ }
+
+ po = pb->object_list + pb->nr_objects;
+ memset(po, 0x0, sizeof(*po));
+
+ if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0)
+ return ret;
+
+ pb->nr_objects++;
+ git_oid_cpy(&po->id, oid);
+ po->hash = name_hash(name);
+
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ if (ret < 0) {
+ giterr_set_oom();
+ return ret;
+ }
+ assert(ret != 0);
+ kh_value(pb->object_ix, pos) = po;
+
+ pb->done = false;
+
+ if (pb->progress_cb) {
+ double current_time = git__timer();
+ double elapsed = current_time - pb->last_progress_report_time;
+
+ if (elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ pb->last_progress_report_time = current_time;
+
+ ret = pb->progress_cb(
+ GIT_PACKBUILDER_ADDING_OBJECTS,
+ pb->nr_objects, 0, pb->progress_cb_payload);
+
+ if (ret)
+ return giterr_set_after_callback(ret);
+ }
+ }
+
+ return 0;
+}
+
+static int get_delta(void **out, git_odb *odb, git_pobject *po)
+{
+ git_odb_object *src = NULL, *trg = NULL;
+ size_t delta_size;
+ void *delta_buf;
+ int error;
+
+ *out = NULL;
+
+ if (git_odb_read(&src, odb, &po->delta->id) < 0 ||
+ git_odb_read(&trg, odb, &po->id) < 0)
+ goto on_error;
+
+ error = git_delta(&delta_buf, &delta_size,
+ git_odb_object_data(src), git_odb_object_size(src),
+ git_odb_object_data(trg), git_odb_object_size(trg),
+ 0);
+
+ if (error < 0 && error != GIT_EBUFS)
+ goto on_error;
+
+ if (error == GIT_EBUFS || delta_size != po->delta_size) {
+ giterr_set(GITERR_INVALID, "Delta size changed");
+ goto on_error;
+ }
+
+ *out = delta_buf;
+
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return 0;
+
+on_error:
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return -1;
+}
+
+static int write_object(
+ git_packbuilder *pb,
+ git_pobject *po,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ git_odb_object *obj = NULL;
+ git_otype type;
+ unsigned char hdr[10], *zbuf = NULL;
+ void *data = NULL;
+ size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len;
+ int error;
+
+ /*
+ * If we have a delta base, let's use the delta to save space.
+ * Otherwise load the whole object. 'data' ends up pointing to
+ * whatever data we want to put into the packfile.
+ */
+ if (po->delta) {
+ if (po->delta_data)
+ data = po->delta_data;
+ else if ((error = get_delta(&data, pb->odb, po)) < 0)
+ goto done;
+
+ data_len = po->delta_size;
+ type = GIT_OBJ_REF_DELTA;
+ } else {
+ if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0)
+ goto done;
+
+ data = (void *)git_odb_object_data(obj);
+ data_len = git_odb_object_size(obj);
+ type = git_odb_object_type(obj);
+ }
+
+ /* Write header */
+ hdr_len = git_packfile__object_header(hdr, data_len, type);
+
+ if ((error = write_cb(hdr, hdr_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0)
+ goto done;
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0)
+ goto done;
+ }
+
+ /* Write data */
+ if (po->z_delta_size) {
+ data_len = po->z_delta_size;
+
+ if ((error = write_cb(data, data_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, data, data_len)) < 0)
+ goto done;
+ } else {
+ zbuf = git__malloc(zbuf_len);
+ GITERR_CHECK_ALLOC(zbuf);
+
+ git_zstream_reset(&pb->zstream);
+ git_zstream_set_input(&pb->zstream, data, data_len);
+
+ while (!git_zstream_done(&pb->zstream)) {
+ if ((error = git_zstream_get_output(zbuf, &zbuf_len, &pb->zstream)) < 0 ||
+ (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0)
+ goto done;
+
+ zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */
+ }
+ }
+
+ /*
+ * If po->delta is true, data is a delta and it is our
+ * responsibility to free it (otherwise it's a git_object's
+ * data). We set po->delta_data to NULL in case we got the
+ * data from there instead of get_delta(). If we didn't,
+ * there's no harm.
+ */
+ if (po->delta) {
+ git__free(data);
+ po->delta_data = NULL;
+ }
+
+ pb->nr_written++;
+
+done:
+ git__free(zbuf);
+ git_odb_object_free(obj);
+ return error;
+}
+
+enum write_one_status {
+ WRITE_ONE_SKIP = -1, /* already written */
+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+ WRITE_ONE_WRITTEN = 1, /* normal */
+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static int write_one(
+ enum write_one_status *status,
+ git_packbuilder *pb,
+ git_pobject *po,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ int error;
+
+ if (po->recursing) {
+ *status = WRITE_ONE_RECURSIVE;
+ return 0;
+ } else if (po->written) {
+ *status = WRITE_ONE_SKIP;
+ return 0;
+ }
+
+ if (po->delta) {
+ po->recursing = 1;
+
+ if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0)
+ return error;
+
+ /* we cannot depend on this one */
+ if (*status == WRITE_ONE_RECURSIVE)
+ po->delta = NULL;
+ }
+
+ *status = WRITE_ONE_WRITTEN;
+ po->written = 1;
+ po->recursing = 0;
+
+ return write_object(pb, po, write_cb, cb_data);
+}
+
+GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp,
+ git_pobject *po)
+{
+ if (po->filled)
+ return;
+ wo[(*endp)++] = po;
+ po->filled = 1;
+}
+
+static void add_descendants_to_write_order(git_pobject **wo, size_t *endp,
+ git_pobject *po)
+{
+ int add_to_order = 1;
+ while (po) {
+ if (add_to_order) {
+ git_pobject *s;
+ /* add this node... */
+ add_to_write_order(wo, endp, po);
+ /* all its siblings... */
+ for (s = po->delta_sibling; s; s = s->delta_sibling) {
+ add_to_write_order(wo, endp, s);
+ }
+ }
+ /* drop down a level to add left subtree nodes if possible */
+ if (po->delta_child) {
+ add_to_order = 1;
+ po = po->delta_child;
+ } else {
+ add_to_order = 0;
+ /* our sibling might have some children, it is next */
+ if (po->delta_sibling) {
+ po = po->delta_sibling;
+ continue;
+ }
+ /* go back to our parent node */
+ po = po->delta;
+ while (po && !po->delta_sibling) {
+ /* we're on the right side of a subtree, keep
+ * going up until we can go right again */
+ po = po->delta;
+ }
+ if (!po) {
+ /* done- we hit our original root node */
+ return;
+ }
+ /* pass it off to sibling at this level */
+ po = po->delta_sibling;
+ }
+ };
+}
+
+static void add_family_to_write_order(git_pobject **wo, size_t *endp,
+ git_pobject *po)
+{
+ git_pobject *root;
+
+ for (root = po; root->delta; root = root->delta)
+ ; /* nothing */
+ add_descendants_to_write_order(wo, endp, root);
+}
+
+static int cb_tag_foreach(const char *name, git_oid *oid, void *data)
+{
+ git_packbuilder *pb = data;
+ git_pobject *po;
+ khiter_t pos;
+
+ GIT_UNUSED(name);
+
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos == kh_end(pb->object_ix))
+ return 0;
+
+ po = kh_value(pb->object_ix, pos);
+ po->tagged = 1;
+
+ /* TODO: peel objects */
+
+ return 0;
+}
+
+static git_pobject **compute_write_order(git_packbuilder *pb)
+{
+ size_t i, wo_end, last_untagged;
+ git_pobject **wo;
+
+ if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL)
+ return NULL;
+
+ for (i = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ po->tagged = 0;
+ po->filled = 0;
+ po->delta_child = NULL;
+ po->delta_sibling = NULL;
+ }
+
+ /*
+ * Fully connect delta_child/delta_sibling network.
+ * Make sure delta_sibling is sorted in the original
+ * recency order.
+ */
+ for (i = pb->nr_objects; i > 0;) {
+ git_pobject *po = &pb->object_list[--i];
+ if (!po->delta)
+ continue;
+ /* Mark me as the first child */
+ po->delta_sibling = po->delta->delta_child;
+ po->delta->delta_child = po;
+ }
+
+ /*
+ * Mark objects that are at the tip of tags.
+ */
+ if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) {
+ git__free(wo);
+ return NULL;
+ }
+
+ /*
+ * Give the objects in the original recency order until
+ * we see a tagged tip.
+ */
+ for (i = wo_end = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ break;
+ add_to_write_order(wo, &wo_end, po);
+ }
+ last_untagged = i;
+
+ /*
+ * Then fill all the tagged tips.
+ */
+ for (; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all remaining commits and tags.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_COMMIT &&
+ po->type != GIT_OBJ_TAG)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all the trees.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_TREE)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * Finally all the rest in really tight order
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (!po->filled)
+ add_family_to_write_order(wo, &wo_end, po);
+ }
+
+ if (wo_end != pb->nr_objects) {
+ git__free(wo);
+ giterr_set(GITERR_INVALID, "invalid write order");
+ return NULL;
+ }
+
+ return wo;
+}
+
+static int write_pack(git_packbuilder *pb,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ git_pobject **write_order;
+ git_pobject *po;
+ enum write_one_status status;
+ struct git_pack_header ph;
+ git_oid entry_oid;
+ size_t i = 0;
+ int error = 0;
+
+ write_order = compute_write_order(pb);
+ if (write_order == NULL)
+ return -1;
+
+ if (!git__is_uint32(pb->nr_objects)) {
+ giterr_set(GITERR_INVALID, "too many objects");
+ return -1;
+ }
+
+ /* Write pack header */
+ ph.hdr_signature = htonl(PACK_SIGNATURE);
+ ph.hdr_version = htonl(PACK_VERSION);
+ ph.hdr_entries = htonl(pb->nr_objects);
+
+ if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0)
+ goto done;
+
+ pb->nr_remaining = pb->nr_objects;
+ do {
+ pb->nr_written = 0;
+ for ( ; i < pb->nr_objects; ++i) {
+ po = write_order[i];
+
+ if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0)
+ goto done;
+ }
+
+ pb->nr_remaining -= pb->nr_written;
+ } while (pb->nr_remaining && i < pb->nr_objects);
+
+ if ((error = git_hash_final(&entry_oid, &pb->ctx)) < 0)
+ goto done;
+
+ error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data);
+
+done:
+ /* if callback cancelled writing, we must still free delta_data */
+ for ( ; i < pb->nr_objects; ++i) {
+ po = write_order[i];
+ if (po->delta_data) {
+ git__free(po->delta_data);
+ po->delta_data = NULL;
+ }
+ }
+
+ git__free(write_order);
+ return error;
+}
+
+static int write_pack_buf(void *buf, size_t size, void *data)
+{
+ git_buf *b = (git_buf *)data;
+ return git_buf_put(b, buf, size);
+}
+
+static int type_size_sort(const void *_a, const void *_b)
+{
+ const git_pobject *a = (git_pobject *)_a;
+ const git_pobject *b = (git_pobject *)_b;
+
+ if (a->type > b->type)
+ return -1;
+ if (a->type < b->type)
+ return 1;
+ if (a->hash > b->hash)
+ return -1;
+ if (a->hash < b->hash)
+ return 1;
+ /*
+ * TODO
+ *
+ if (a->preferred_base > b->preferred_base)
+ return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
+ */
+ if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
+ return 1;
+ return a < b ? -1 : (a > b); /* newest first */
+}
+
+static int delta_cacheable(
+ git_packbuilder *pb,
+ size_t src_size,
+ size_t trg_size,
+ size_t delta_size)
+{
+ size_t new_size;
+
+ if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size))
+ return 0;
+
+ if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size)
+ return 0;
+
+ if (delta_size < pb->cache_max_small_delta_size)
+ return 1;
+
+ /* cache delta, if objects are large enough compared to delta size */
+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+ return 1;
+
+ return 0;
+}
+
+static int try_delta(git_packbuilder *pb, struct unpacked *trg,
+ struct unpacked *src, size_t max_depth,
+ size_t *mem_usage, int *ret)
+{
+ git_pobject *trg_object = trg->object;
+ git_pobject *src_object = src->object;
+ git_odb_object *obj;
+ size_t trg_size, src_size, delta_size, sizediff, max_size, sz;
+ size_t ref_depth;
+ void *delta_buf;
+
+ /* Don't bother doing diffs between different types */
+ if (trg_object->type != src_object->type) {
+ *ret = -1;
+ return 0;
+ }
+
+ *ret = 0;
+
+ /* TODO: support reuse-delta */
+
+ /* Let's not bust the allowed depth. */
+ if (src->depth >= max_depth)
+ return 0;
+
+ /* Now some size filtering heuristics. */
+ trg_size = trg_object->size;
+ if (!trg_object->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_object->delta_size;
+ ref_depth = trg->depth;
+ }
+
+ max_size = (uint64_t)max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
+ if (max_size == 0)
+ return 0;
+
+ src_size = src_object->size;
+ sizediff = src_size < trg_size ? trg_size - src_size : 0;
+ if (sizediff >= max_size)
+ return 0;
+ if (trg_size < src_size / 32)
+ return 0;
+
+ /* Load data if not already done */
+ if (!trg->data) {
+ if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0)
+ return -1;
+
+ sz = git_odb_object_size(obj);
+ trg->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(trg->data);
+ memcpy(trg->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != trg_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent target object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->data) {
+ size_t obj_sz;
+
+ if (git_odb_read(&obj, pb->odb, &src_object->id) < 0 ||
+ !git__is_ulong(obj_sz = git_odb_object_size(obj)))
+ return -1;
+
+ sz = obj_sz;
+ src->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(src->data);
+ memcpy(src->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != src_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent source object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->index) {
+ if (git_delta_index_init(&src->index, src->data, src_size) < 0)
+ return 0; /* suboptimal pack - out of memory */
+
+ *mem_usage += git_delta_index_size(src->index);
+ }
+
+ if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size,
+ max_size) < 0)
+ return 0;
+
+ if (trg_object->delta) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_object->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ git__free(delta_buf);
+ return 0;
+ }
+ }
+
+ git_packbuilder__cache_lock(pb);
+ if (trg_object->delta_data) {
+ git__free(trg_object->delta_data);
+ assert(pb->delta_cache_size >= trg_object->delta_size);
+ pb->delta_cache_size -= trg_object->delta_size;
+ trg_object->delta_data = NULL;
+ }
+ if (delta_cacheable(pb, src_size, trg_size, delta_size)) {
+ bool overflow = git__add_sizet_overflow(
+ &pb->delta_cache_size, pb->delta_cache_size, delta_size);
+
+ git_packbuilder__cache_unlock(pb);
+
+ if (overflow) {
+ git__free(delta_buf);
+ return -1;
+ }
+
+ trg_object->delta_data = git__realloc(delta_buf, delta_size);
+ GITERR_CHECK_ALLOC(trg_object->delta_data);
+ } else {
+ /* create delta when writing the pack */
+ git_packbuilder__cache_unlock(pb);
+ git__free(delta_buf);
+ }
+
+ trg_object->delta = src_object;
+ trg_object->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
+ *ret = 1;
+ return 0;
+}
+
+static size_t check_delta_limit(git_pobject *me, size_t n)
+{
+ git_pobject *child = me->delta_child;
+ size_t m = n;
+
+ while (child) {
+ size_t c = check_delta_limit(child, n + 1);
+ if (m < c)
+ m = c;
+ child = child->delta_sibling;
+ }
+ return m;
+}
+
+static size_t free_unpacked(struct unpacked *n)
+{
+ size_t freed_mem = 0;
+
+ if (n->index) {
+ freed_mem += git_delta_index_size(n->index);
+ git_delta_index_free(n->index);
+ }
+ n->index = NULL;
+
+ if (n->data) {
+ freed_mem += n->object->size;
+ git__free(n->data);
+ n->data = NULL;
+ }
+ n->object = NULL;
+ n->depth = 0;
+ return freed_mem;
+}
+
+static int report_delta_progress(
+ git_packbuilder *pb, uint32_t count, bool force)
+{
+ int ret;
+
+ if (pb->progress_cb) {
+ double current_time = git__timer();
+ double elapsed = current_time - pb->last_progress_report_time;
+
+ if (force || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ pb->last_progress_report_time = current_time;
+
+ ret = pb->progress_cb(
+ GIT_PACKBUILDER_DELTAFICATION,
+ count, pb->nr_objects, pb->progress_cb_payload);
+
+ if (ret)
+ return giterr_set_after_callback(ret);
+ }
+ }
+
+ return 0;
+}
+
+static int find_deltas(git_packbuilder *pb, git_pobject **list,
+ size_t *list_size, size_t window, size_t depth)
+{
+ git_pobject *po;
+ git_buf zbuf = GIT_BUF_INIT;
+ struct unpacked *array;
+ size_t idx = 0, count = 0;
+ size_t mem_usage = 0;
+ size_t i;
+ int error = -1;
+
+ array = git__calloc(window, sizeof(struct unpacked));
+ GITERR_CHECK_ALLOC(array);
+
+ for (;;) {
+ struct unpacked *n = array + idx;
+ size_t max_depth, j, best_base = SIZE_MAX;
+
+ git_packbuilder__progress_lock(pb);
+ if (!*list_size) {
+ git_packbuilder__progress_unlock(pb);
+ break;
+ }
+
+ pb->nr_deltified += 1;
+ report_delta_progress(pb, pb->nr_deltified, false);
+
+ po = *list++;
+ (*list_size)--;
+ git_packbuilder__progress_unlock(pb);
+
+ mem_usage -= free_unpacked(n);
+ n->object = po;
+
+ while (pb->window_memory_limit &&
+ mem_usage > pb->window_memory_limit &&
+ count > 1) {
+ size_t tail = (idx + window - count) % window;
+ mem_usage -= free_unpacked(array + tail);
+ count--;
+ }
+
+ /*
+ * If the current object is at pack edge, take the depth the
+ * objects that depend on the current object into account
+ * otherwise they would become too deep.
+ */
+ max_depth = depth;
+ if (po->delta_child) {
+ size_t delta_limit = check_delta_limit(po, 0);
+
+ if (delta_limit > max_depth)
+ goto next;
+
+ max_depth -= delta_limit;
+ }
+
+ j = window;
+ while (--j > 0) {
+ int ret;
+ size_t other_idx = idx + j;
+ struct unpacked *m;
+
+ if (other_idx >= window)
+ other_idx -= window;
+
+ m = array + other_idx;
+ if (!m->object)
+ break;
+
+ if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0)
+ goto on_error;
+ if (ret < 0)
+ break;
+ else if (ret > 0)
+ best_base = other_idx;
+ }
+
+ /*
+ * If we decided to cache the delta data, then it is best
+ * to compress it right away. First because we have to do
+ * it anyway, and doing it here while we're threaded will
+ * save a lot of time in the non threaded write phase,
+ * as well as allow for caching more deltas within
+ * the same cache size limit.
+ * ...
+ * But only if not writing to stdout, since in that case
+ * the network is most likely throttling writes anyway,
+ * and therefore it is best to go to the write phase ASAP
+ * instead, as we can afford spending more time compressing
+ * between writes at that moment.
+ */
+ if (po->delta_data) {
+ if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0)
+ goto on_error;
+
+ git__free(po->delta_data);
+ po->delta_data = git__malloc(zbuf.size);
+ GITERR_CHECK_ALLOC(po->delta_data);
+
+ memcpy(po->delta_data, zbuf.ptr, zbuf.size);
+ po->z_delta_size = zbuf.size;
+ git_buf_clear(&zbuf);
+
+ git_packbuilder__cache_lock(pb);
+ pb->delta_cache_size -= po->delta_size;
+ pb->delta_cache_size += po->z_delta_size;
+ git_packbuilder__cache_unlock(pb);
+ }
+
+ /*
+ * If we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ */
+ if (po->delta && max_depth <= n->depth)
+ continue;
+
+ /*
+ * Move the best delta base up in the window, after the
+ * currently deltified object, to keep it longer. It will
+ * be the first base object to be attempted next.
+ */
+ if (po->delta) {
+ struct unpacked swap = array[best_base];
+ size_t dist = (window + idx - best_base) % window;
+ size_t dst = best_base;
+ while (dist--) {
+ size_t src = (dst + 1) % window;
+ array[dst] = array[src];
+ dst = src;
+ }
+ array[dst] = swap;
+ }
+
+ next:
+ idx++;
+ if (count + 1 < window)
+ count++;
+ if (idx >= window)
+ idx = 0;
+ }
+ error = 0;
+
+on_error:
+ for (i = 0; i < window; ++i) {
+ git__free(array[i].index);
+ git__free(array[i].data);
+ }
+ git__free(array);
+ git_buf_free(&zbuf);
+
+ return error;
+}
+
+#ifdef GIT_THREADS
+
+struct thread_params {
+ git_thread thread;
+ git_packbuilder *pb;
+
+ git_pobject **list;
+
+ git_cond cond;
+ git_mutex mutex;
+
+ size_t list_size;
+ size_t remaining;
+
+ size_t window;
+ size_t depth;
+ size_t working;
+ size_t data_ready;
+};
+
+static void *threaded_find_deltas(void *arg)
+{
+ struct thread_params *me = arg;
+
+ while (me->remaining) {
+ if (find_deltas(me->pb, me->list, &me->remaining,
+ me->window, me->depth) < 0) {
+ ; /* TODO */
+ }
+
+ git_packbuilder__progress_lock(me->pb);
+ me->working = 0;
+ git_cond_signal(&me->pb->progress_cond);
+ git_packbuilder__progress_unlock(me->pb);
+
+ if (git_mutex_lock(&me->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ return NULL;
+ }
+
+ while (!me->data_ready)
+ git_cond_wait(&me->cond, &me->mutex);
+
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ me->data_ready = 0;
+ git_mutex_unlock(&me->mutex);
+ }
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
+}
+
+static int ll_find_deltas(git_packbuilder *pb, git_pobject **list,
+ size_t list_size, size_t window, size_t depth)
+{
+ struct thread_params *p;
+ size_t i;
+ int ret, active_threads = 0;
+
+ if (!pb->nr_threads)
+ pb->nr_threads = git_online_cpus();
+
+ if (pb->nr_threads <= 1) {
+ find_deltas(pb, list, &list_size, window, depth);
+ return 0;
+ }
+
+ p = git__mallocarray(pb->nr_threads, sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ /* Partition the work among the threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ size_t sub_size = list_size / (pb->nr_threads - i);
+
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < pb->nr_threads)
+ sub_size = 0;
+
+ p[i].pb = pb;
+ p[i].window = window;
+ p[i].depth = depth;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ if (!p[i].list_size)
+ continue;
+
+ git_mutex_init(&p[i].mutex);
+ git_cond_init(&p[i].cond);
+
+ ret = git_thread_create(&p[i].thread,
+ threaded_find_deltas, &p[i]);
+ if (ret) {
+ giterr_set(GITERR_THREAD, "unable to create thread");
+ return -1;
+ }
+ active_threads++;
+ }
+
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ size_t sub_size = 0;
+
+ /* Start by locating a thread that has transitioned its
+ * 'working' flag from 1 -> 0. This indicates that it is
+ * ready to receive more work using our work-stealing
+ * algorithm. */
+ git_packbuilder__progress_lock(pb);
+ for (;;) {
+ for (i = 0; !target && i < pb->nr_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ git_cond_wait(&pb->progress_cond, &pb->progress_mutex);
+ }
+
+ /* At this point we hold the progress lock and have located
+ * a thread to receive more work. We still need to locate a
+ * thread from which to steal work (the victim). */
+ for (i = 0; i < pb->nr_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ git_packbuilder__progress_unlock(pb);
+
+ if (git_mutex_lock(&target->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ git__free(p);
+ return -1;
+ }
+
+ target->data_ready = 1;
+ git_cond_signal(&target->cond);
+ git_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
+ git_thread_join(&target->thread, NULL);
+ git_cond_free(&target->cond);
+ git_mutex_free(&target->mutex);
+ active_threads--;
+ }
+ }
+
+ git__free(p);
+ return 0;
+}
+
+#else
+#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d)
+#endif
+
+static int prepare_pack(git_packbuilder *pb)
+{
+ git_pobject **delta_list;
+ size_t i, n = 0;
+
+ if (pb->nr_objects == 0 || pb->done)
+ return 0; /* nothing to do */
+
+ /*
+ * Although we do not report progress during deltafication, we
+ * at least report that we are in the deltafication stage
+ */
+ if (pb->progress_cb)
+ pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload);
+
+ delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list));
+ GITERR_CHECK_ALLOC(delta_list);
+
+ for (i = 0; i < pb->nr_objects; ++i) {
+ git_pobject *po = pb->object_list + i;
+
+ /* Make sure the item is within our size limits */
+ if (po->size < 50 || po->size > pb->big_file_threshold)
+ continue;
+
+ delta_list[n++] = po;
+ }
+
+ if (n > 1) {
+ git__tsort((void **)delta_list, n, type_size_sort);
+ if (ll_find_deltas(pb, delta_list, n,
+ GIT_PACK_WINDOW + 1,
+ GIT_PACK_DEPTH) < 0) {
+ git__free(delta_list);
+ return -1;
+ }
+ }
+
+ report_delta_progress(pb, pb->nr_objects, true);
+
+ pb->done = true;
+ git__free(delta_list);
+ return 0;
+}
+
+#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
+
+int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
+{
+ PREPARE_PACK;
+ return write_pack(pb, cb, payload);
+}
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
+{
+ PREPARE_PACK;
+ git_buf_sanitize(buf);
+ return write_pack(pb, &write_pack_buf, buf);
+}
+
+static int write_cb(void *buf, size_t len, void *payload)
+{
+ struct pack_write_context *ctx = payload;
+ return git_indexer_append(ctx->indexer, buf, len, ctx->stats);
+}
+
+int git_packbuilder_write(
+ git_packbuilder *pb,
+ const char *path,
+ unsigned int mode,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload)
+{
+ git_indexer *indexer;
+ git_transfer_progress stats;
+ struct pack_write_context ctx;
+
+ PREPARE_PACK;
+
+ if (git_indexer_new(
+ &indexer, path, mode, pb->odb, progress_cb, progress_cb_payload) < 0)
+ return -1;
+
+ ctx.indexer = indexer;
+ ctx.stats = &stats;
+
+ if (git_packbuilder_foreach(pb, write_cb, &ctx) < 0 ||
+ git_indexer_commit(indexer, &stats) < 0) {
+ git_indexer_free(indexer);
+ return -1;
+ }
+
+ git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer));
+
+ git_indexer_free(indexer);
+ return 0;
+}
+
+#undef PREPARE_PACK
+
+const git_oid *git_packbuilder_hash(git_packbuilder *pb)
+{
+ return &pb->pack_oid;
+}
+
+
+static int cb_tree_walk(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ int error;
+ struct tree_walk_context *ctx = payload;
+
+ /* A commit inside a tree represents a submodule commit and should be skipped. */
+ if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT)
+ return 0;
+
+ if (!(error = git_buf_sets(&ctx->buf, root)) &&
+ !(error = git_buf_puts(&ctx->buf, git_tree_entry_name(entry))))
+ error = git_packbuilder_insert(
+ ctx->pb, git_tree_entry_id(entry), git_buf_cstr(&ctx->buf));
+
+ return error;
+}
+
+int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid)
+{
+ git_commit *commit;
+
+ if (git_commit_lookup(&commit, pb->repo, oid) < 0 ||
+ git_packbuilder_insert(pb, oid, NULL) < 0)
+ return -1;
+
+ if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0)
+ return -1;
+
+ git_commit_free(commit);
+ return 0;
+}
+
+int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
+{
+ int error;
+ git_tree *tree = NULL;
+ struct tree_walk_context context = { pb, GIT_BUF_INIT };
+
+ if (!(error = git_tree_lookup(&tree, pb->repo, oid)) &&
+ !(error = git_packbuilder_insert(pb, oid, NULL)))
+ error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context);
+
+ git_tree_free(tree);
+ git_buf_free(&context.buf);
+ return error;
+}
+
+int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name)
+{
+ git_object *obj;
+ int error;
+
+ assert(pb && id);
+
+ if ((error = git_object_lookup(&obj, pb->repo, id, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ switch (git_object_type(obj)) {
+ case GIT_OBJ_BLOB:
+ error = git_packbuilder_insert(pb, id, name);
+ break;
+ case GIT_OBJ_TREE:
+ error = git_packbuilder_insert_tree(pb, id);
+ break;
+ case GIT_OBJ_COMMIT:
+ error = git_packbuilder_insert_commit(pb, id);
+ break;
+ case GIT_OBJ_TAG:
+ if ((error = git_packbuilder_insert(pb, id, name)) < 0)
+ goto cleanup;
+ error = git_packbuilder_insert_recur(pb, git_tag_target_id((git_tag *) obj), NULL);
+ break;
+
+ default:
+ giterr_set(GITERR_INVALID, "unknown object type");
+ error = -1;
+ }
+
+cleanup:
+ git_object_free(obj);
+ return error;
+}
+
+size_t git_packbuilder_object_count(git_packbuilder *pb)
+{
+ return pb->nr_objects;
+}
+
+size_t git_packbuilder_written(git_packbuilder *pb)
+{
+ return pb->nr_written;
+}
+
+int lookup_walk_object(git_walk_object **out, git_packbuilder *pb, const git_oid *id)
+{
+ git_walk_object *obj;
+
+ obj = git_pool_mallocz(&pb->object_pool, 1);
+ if (!obj) {
+ giterr_set_oom();
+ return -1;
+ }
+
+ git_oid_cpy(&obj->id, id);
+
+ *out = obj;
+ return 0;
+}
+
+static int retrieve_object(git_walk_object **out, git_packbuilder *pb, const git_oid *id)
+{
+ int error;
+ khiter_t pos;
+ git_walk_object *obj;
+
+ pos = git_oidmap_lookup_index(pb->walk_objects, id);
+ if (git_oidmap_valid_index(pb->walk_objects, pos)) {
+ obj = git_oidmap_value_at(pb->walk_objects, pos);
+ } else {
+ if ((error = lookup_walk_object(&obj, pb, id)) < 0)
+ return error;
+
+ git_oidmap_insert(pb->walk_objects, &obj->id, obj, error);
+ }
+
+ *out = obj;
+ return 0;
+}
+
+static int mark_blob_uninteresting(git_packbuilder *pb, const git_oid *id)
+{
+ int error;
+ git_walk_object *obj;
+
+ if ((error = retrieve_object(&obj, pb, id)) < 0)
+ return error;
+
+ obj->uninteresting = 1;
+
+ return 0;
+}
+
+static int mark_tree_uninteresting(git_packbuilder *pb, const git_oid *id)
+{
+ git_walk_object *obj;
+ git_tree *tree;
+ int error;
+ size_t i;
+
+ if ((error = retrieve_object(&obj, pb, id)) < 0)
+ return error;
+
+ if (obj->uninteresting)
+ return 0;
+
+ obj->uninteresting = 1;
+
+ if ((error = git_tree_lookup(&tree, pb->repo, id)) < 0)
+ return error;
+
+ for (i = 0; i < git_tree_entrycount(tree); i++) {
+ const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
+ const git_oid *entry_id = git_tree_entry_id(entry);
+ switch (git_tree_entry_type(entry)) {
+ case GIT_OBJ_TREE:
+ if ((error = mark_tree_uninteresting(pb, entry_id)) < 0)
+ goto cleanup;
+ break;
+ case GIT_OBJ_BLOB:
+ if ((error = mark_blob_uninteresting(pb, entry_id)) < 0)
+ goto cleanup;
+ break;
+ default:
+ /* it's a submodule or something unknown, we don't want it */
+ ;
+ }
+ }
+
+cleanup:
+ git_tree_free(tree);
+ return error;
+}
+
+/*
+ * Mark the edges of the graph uninteresting. Since we start from a
+ * git_revwalk, the commits are already uninteresting, but we need to
+ * mark the trees and blobs.
+ */
+static int mark_edges_uninteresting(git_packbuilder *pb, git_commit_list *commits)
+{
+ int error;
+ git_commit_list *list;
+ git_commit *commit;
+
+ for (list = commits; list; list = list->next) {
+ if (!list->item->uninteresting)
+ continue;
+
+ if ((error = git_commit_lookup(&commit, pb->repo, &list->item->oid)) < 0)
+ return error;
+
+ error = mark_tree_uninteresting(pb, git_commit_tree_id(commit));
+ git_commit_free(commit);
+
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+int insert_tree(git_packbuilder *pb, git_tree *tree)
+{
+ size_t i;
+ int error;
+ git_tree *subtree;
+ git_walk_object *obj;
+ const char *name;
+
+ if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0)
+ return error;
+
+ if (obj->seen)
+ return 0;
+
+ obj->seen = 1;
+
+ if ((error = git_packbuilder_insert(pb, &obj->id, NULL)))
+ return error;
+
+ for (i = 0; i < git_tree_entrycount(tree); i++) {
+ const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
+ const git_oid *entry_id = git_tree_entry_id(entry);
+ switch (git_tree_entry_type(entry)) {
+ case GIT_OBJ_TREE:
+ if ((error = git_tree_lookup(&subtree, pb->repo, entry_id)) < 0)
+ return error;
+
+ error = insert_tree(pb, subtree);
+ git_tree_free(subtree);
+
+ if (error < 0)
+ return error;
+
+ break;
+ case GIT_OBJ_BLOB:
+ name = git_tree_entry_name(entry);
+ if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0)
+ return error;
+ break;
+ default:
+ /* it's a submodule or something unknown, we don't want it */
+ ;
+ }
+ }
+
+
+ return error;
+}
+
+int insert_commit(git_packbuilder *pb, git_walk_object *obj)
+{
+ int error;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ obj->seen = 1;
+
+ if ((error = git_packbuilder_insert(pb, &obj->id, NULL)) < 0)
+ return error;
+
+ if ((error = git_commit_lookup(&commit, pb->repo, &obj->id)) < 0)
+ return error;
+
+ if ((error = git_tree_lookup(&tree, pb->repo, git_commit_tree_id(commit))) < 0)
+ goto cleanup;
+
+ if ((error = insert_tree(pb, tree)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_commit_free(commit);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk)
+{
+ int error;
+ git_oid id;
+ git_walk_object *obj;
+
+ assert(pb && walk);
+
+ if ((error = mark_edges_uninteresting(pb, walk->user_input)) < 0)
+ return error;
+
+ /*
+ * TODO: git marks the parents of the edges
+ * uninteresting. This may provide a speed advantage, but does
+ * seem to assume the remote does not have a single-commit
+ * history on the other end.
+ */
+
+ /* walk down each tree up to the blobs and insert them, stopping when uninteresting */
+ while ((error = git_revwalk_next(&id, walk)) == 0) {
+ if ((error = retrieve_object(&obj, pb, &id)) < 0)
+ return error;
+
+ if (obj->seen || obj->uninteresting)
+ continue;
+
+ if ((error = insert_commit(pb, obj)) < 0)
+ return error;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return 0;
+}
+
+int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload)
+{
+ if (!pb)
+ return -1;
+
+ pb->progress_cb = progress_cb;
+ pb->progress_cb_payload = progress_cb_payload;
+
+ return 0;
+}
+
+void git_packbuilder_free(git_packbuilder *pb)
+{
+ if (pb == NULL)
+ return;
+
+#ifdef GIT_THREADS
+
+ git_mutex_free(&pb->cache_mutex);
+ git_mutex_free(&pb->progress_mutex);
+ git_cond_free(&pb->progress_cond);
+
+#endif
+
+ if (pb->odb)
+ git_odb_free(pb->odb);
+
+ if (pb->object_ix)
+ git_oidmap_free(pb->object_ix);
+
+ if (pb->object_list)
+ git__free(pb->object_list);
+
+ git_oidmap_free(pb->walk_objects);
+ git_pool_clear(&pb->object_pool);
+
+ git_hash_ctx_cleanup(&pb->ctx);
+ git_zstream_free(&pb->zstream);
+
+ git__free(pb);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_pack_objects_h__
+#define INCLUDE_pack_objects_h__
+
+#include "common.h"
+
+#include "buffer.h"
+#include "hash.h"
+#include "oidmap.h"
+#include "netops.h"
+#include "zstream.h"
+#include "pool.h"
+
+#include "git2/oid.h"
+#include "git2/pack.h"
+
+#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */
+#define GIT_PACK_DEPTH 50 /* max delta depth */
+#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024)
+#define GIT_PACK_DELTA_CACHE_LIMIT 1000
+#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024)
+
+typedef struct git_pobject {
+ git_oid id;
+ git_otype type;
+ git_off_t offset;
+
+ size_t size;
+
+ unsigned int hash; /* name hint hash */
+
+ struct git_pobject *delta; /* delta base object */
+ struct git_pobject *delta_child; /* deltified objects who bases me */
+ struct git_pobject *delta_sibling; /* other deltified objects
+ * who uses the same base as
+ * me */
+
+ void *delta_data;
+ size_t delta_size;
+ size_t z_delta_size;
+
+ int written:1,
+ recursing:1,
+ tagged:1,
+ filled:1;
+} git_pobject;
+
+typedef struct {
+ git_oid id;
+ unsigned int uninteresting:1,
+ seen:1;
+} git_walk_object;
+
+struct git_packbuilder {
+ git_repository *repo; /* associated repository */
+ git_odb *odb; /* associated object database */
+
+ git_hash_ctx ctx;
+ git_zstream zstream;
+
+ uint32_t nr_objects,
+ nr_deltified,
+ nr_written,
+ nr_remaining;
+
+ size_t nr_alloc;
+
+ git_pobject *object_list;
+
+ git_oidmap *object_ix;
+
+ git_oidmap *walk_objects;
+ git_pool object_pool;
+
+ git_oid pack_oid; /* hash of written pack */
+
+ /* synchronization objects */
+ git_mutex cache_mutex;
+ git_mutex progress_mutex;
+ git_cond progress_cond;
+
+ /* configs */
+ size_t delta_cache_size;
+ size_t max_delta_cache_size;
+ size_t cache_max_small_delta_size;
+ size_t big_file_threshold;
+ size_t window_memory_limit;
+
+ unsigned int nr_threads; /* nr of threads to use */
+
+ git_packbuilder_progress progress_cb;
+ void *progress_cb_payload;
+ double last_progress_report_time; /* the time progress was last reported */
+
+ bool done;
+};
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
+
+#endif /* INCLUDE_pack_objects_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "odb.h"
+#include "pack.h"
+#include "delta.h"
+#include "sha1_lookup.h"
+#include "mwindow.h"
+#include "fileops.h"
+#include "oid.h"
+
+#include <zlib.h>
+
+GIT__USE_OFFMAP
+GIT__USE_OIDMAP
+
+static int packfile_open(struct git_pack_file *p);
+static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n);
+static int packfile_unpack_compressed(
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ size_t size,
+ git_otype type);
+
+/* Can find the offset of an object given
+ * a prefix of an identifier.
+ * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid
+ * is ambiguous within the pack.
+ * This method assumes that len is between
+ * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ.
+ */
+static int pack_entry_find_offset(
+ git_off_t *offset_out,
+ git_oid *found_oid,
+ struct git_pack_file *p,
+ const git_oid *short_oid,
+ size_t len);
+
+static int packfile_error(const char *message)
+{
+ giterr_set(GITERR_ODB, "Invalid pack file - %s", message);
+ return -1;
+}
+
+/********************
+ * Delta base cache
+ ********************/
+
+static git_pack_cache_entry *new_cache_object(git_rawobj *source)
+{
+ git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry));
+ if (!e)
+ return NULL;
+
+ git_atomic_inc(&e->refcount);
+ memcpy(&e->raw, source, sizeof(git_rawobj));
+
+ return e;
+}
+
+static void free_cache_object(void *o)
+{
+ git_pack_cache_entry *e = (git_pack_cache_entry *)o;
+
+ if (e != NULL) {
+ assert(e->refcount.val == 0);
+ git__free(e->raw.data);
+ git__free(e);
+ }
+}
+
+static void cache_free(git_pack_cache *cache)
+{
+ khiter_t k;
+
+ if (cache->entries) {
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (kh_exist(cache->entries, k))
+ free_cache_object(kh_value(cache->entries, k));
+ }
+
+ git_offmap_free(cache->entries);
+ cache->entries = NULL;
+ }
+}
+
+static int cache_init(git_pack_cache *cache)
+{
+ cache->entries = git_offmap_alloc();
+ GITERR_CHECK_ALLOC(cache->entries);
+
+ cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT;
+
+ if (git_mutex_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize pack cache mutex");
+
+ git__free(cache->entries);
+ cache->entries = NULL;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset)
+{
+ khiter_t k;
+ git_pack_cache_entry *entry = NULL;
+
+ if (git_mutex_lock(&cache->lock) < 0)
+ return NULL;
+
+ k = kh_get(off, cache->entries, offset);
+ if (k != kh_end(cache->entries)) { /* found it */
+ entry = kh_value(cache->entries, k);
+ git_atomic_inc(&entry->refcount);
+ entry->last_usage = cache->use_ctr++;
+ }
+ git_mutex_unlock(&cache->lock);
+
+ return entry;
+}
+
+/* Run with the cache lock held */
+static void free_lowest_entry(git_pack_cache *cache)
+{
+ git_pack_cache_entry *entry;
+ khiter_t k;
+
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (!kh_exist(cache->entries, k))
+ continue;
+
+ entry = kh_value(cache->entries, k);
+
+ if (entry && entry->refcount.val == 0) {
+ cache->memory_used -= entry->raw.len;
+ kh_del(off, cache->entries, k);
+ free_cache_object(entry);
+ }
+ }
+}
+
+static int cache_add(
+ git_pack_cache_entry **cached_out,
+ git_pack_cache *cache,
+ git_rawobj *base,
+ git_off_t offset)
+{
+ git_pack_cache_entry *entry;
+ int error, exists = 0;
+ khiter_t k;
+
+ if (base->len > GIT_PACK_CACHE_SIZE_LIMIT)
+ return -1;
+
+ entry = new_cache_object(base);
+ if (entry) {
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock cache");
+ git__free(entry);
+ return -1;
+ }
+ /* Add it to the cache if nobody else has */
+ exists = kh_get(off, cache->entries, offset) != kh_end(cache->entries);
+ if (!exists) {
+ while (cache->memory_used + base->len > cache->memory_limit)
+ free_lowest_entry(cache);
+
+ k = kh_put(off, cache->entries, offset, &error);
+ assert(error != 0);
+ kh_value(cache->entries, k) = entry;
+ cache->memory_used += entry->raw.len;
+
+ *cached_out = entry;
+ }
+ git_mutex_unlock(&cache->lock);
+ /* Somebody beat us to adding it into the cache */
+ if (exists) {
+ git__free(entry);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/***********************************************************
+ *
+ * PACK INDEX METHODS
+ *
+ ***********************************************************/
+
+static void pack_index_free(struct git_pack_file *p)
+{
+ if (p->oids) {
+ git__free(p->oids);
+ p->oids = NULL;
+ }
+ if (p->index_map.data) {
+ git_futils_mmap_free(&p->index_map);
+ p->index_map.data = NULL;
+ }
+}
+
+static int pack_index_check(const char *path, struct git_pack_file *p)
+{
+ struct git_pack_idx_header *hdr;
+ uint32_t version, nr, i, *index;
+ void *idx_map;
+ size_t idx_size;
+ struct stat st;
+ int error;
+ /* TODO: properly open the file without access time using O_NOATIME */
+ git_file fd = git_futils_open_ro(path);
+ if (fd < 0)
+ return fd;
+
+ if (p_fstat(fd, &st) < 0) {
+ p_close(fd);
+ giterr_set(GITERR_OS, "Unable to stat pack index '%s'", path);
+ return -1;
+ }
+
+ if (!S_ISREG(st.st_mode) ||
+ !git__is_sizet(st.st_size) ||
+ (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20)
+ {
+ p_close(fd);
+ giterr_set(GITERR_ODB, "Invalid pack index '%s'", path);
+ return -1;
+ }
+
+ error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size);
+
+ p_close(fd);
+
+ if (error < 0)
+ return error;
+
+ hdr = idx_map = p->index_map.data;
+
+ if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
+ version = ntohl(hdr->idx_version);
+
+ if (version < 2 || version > 2) {
+ git_futils_mmap_free(&p->index_map);
+ return packfile_error("unsupported index version");
+ }
+
+ } else
+ version = 1;
+
+ nr = 0;
+ index = idx_map;
+
+ if (version > 1)
+ index += 2; /* skip index header */
+
+ for (i = 0; i < 256; i++) {
+ uint32_t n = ntohl(index[i]);
+ if (n < nr) {
+ git_futils_mmap_free(&p->index_map);
+ return packfile_error("index is non-monotonic");
+ }
+ nr = n;
+ }
+
+ if (version == 1) {
+ /*
+ * Total size:
+ * - 256 index entries 4 bytes each
+ * - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
+ */
+ if (idx_size != 4*256 + nr * 24 + 20 + 20) {
+ git_futils_mmap_free(&p->index_map);
+ return packfile_error("index is corrupted");
+ }
+ } else if (version == 2) {
+ /*
+ * Minimum size:
+ * - 8 bytes of header
+ * - 256 index entries 4 bytes each
+ * - 20-byte sha1 entry * nr
+ * - 4-byte crc entry * nr
+ * - 4-byte offset entry * nr
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
+ * And after the 4-byte offset table might be a
+ * variable sized table containing 8-byte entries
+ * for offsets larger than 2^31.
+ */
+ unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
+ unsigned long max_size = min_size;
+
+ if (nr)
+ max_size += (nr - 1)*8;
+
+ if (idx_size < min_size || idx_size > max_size) {
+ git_futils_mmap_free(&p->index_map);
+ return packfile_error("wrong index size");
+ }
+ }
+
+ p->num_objects = nr;
+ p->index_version = version;
+ return 0;
+}
+
+static int pack_index_open(struct git_pack_file *p)
+{
+ int error = 0;
+ size_t name_len;
+ git_buf idx_name = GIT_BUF_INIT;
+
+ if (p->index_version > -1)
+ return 0;
+
+ name_len = strlen(p->pack_name);
+ assert(name_len > strlen(".pack")); /* checked by git_pack_file alloc */
+
+ git_buf_grow(&idx_name, name_len);
+ git_buf_put(&idx_name, p->pack_name, name_len - strlen(".pack"));
+ git_buf_puts(&idx_name, ".idx");
+ if (git_buf_oom(&idx_name)) {
+ giterr_set_oom();
+ return -1;
+ }
+
+ if ((error = git_mutex_lock(&p->lock)) < 0) {
+ git_buf_free(&idx_name);
+ return error;
+ }
+
+ if (p->index_version == -1)
+ error = pack_index_check(idx_name.ptr, p);
+
+ git_buf_free(&idx_name);
+
+ git_mutex_unlock(&p->lock);
+
+ return error;
+}
+
+static unsigned char *pack_window_open(
+ struct git_pack_file *p,
+ git_mwindow **w_cursor,
+ git_off_t offset,
+ unsigned int *left)
+{
+ if (p->mwf.fd == -1 && packfile_open(p) < 0)
+ return NULL;
+
+ /* Since packfiles end in a hash of their content and it's
+ * pointless to ask for an offset into the middle of that
+ * hash, and the pack_window_contains function above wouldn't match
+ * don't allow an offset too close to the end of the file.
+ *
+ * Don't allow a negative offset, as that means we've wrapped
+ * around.
+ */
+ if (offset > (p->mwf.size - 20))
+ return NULL;
+ if (offset < 0)
+ return NULL;
+
+ return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left);
+ }
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ * - first byte: low four bits are "size",
+ * then three bits of "type",
+ * with the high bit being "size continues".
+ * - each byte afterwards: low seven bits are size continuation,
+ * with the high bit being "size continues"
+ */
+size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type)
+{
+ unsigned char *hdr_base;
+ unsigned char c;
+
+ assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA);
+
+ /* TODO: add support for chunked objects; see git.git 6c0d19b1 */
+
+ c = (unsigned char)((type << 4) | (size & 15));
+ size >>= 4;
+ hdr_base = hdr;
+
+ while (size) {
+ *hdr++ = c | 0x80;
+ c = size & 0x7f;
+ size >>= 7;
+ }
+ *hdr++ = c;
+
+ return (hdr - hdr_base);
+}
+
+
+static int packfile_unpack_header1(
+ unsigned long *usedp,
+ size_t *sizep,
+ git_otype *type,
+ const unsigned char *buf,
+ unsigned long len)
+{
+ unsigned shift;
+ unsigned long size, c;
+ unsigned long used = 0;
+
+ c = buf[used++];
+ *type = (c >> 4) & 7;
+ size = c & 15;
+ shift = 4;
+ while (c & 0x80) {
+ if (len <= used) {
+ giterr_set(GITERR_ODB, "buffer too small");
+ return GIT_EBUFS;
+ }
+
+ if (bitsizeof(long) <= shift) {
+ *usedp = 0;
+ giterr_set(GITERR_ODB, "packfile corrupted");
+ return -1;
+ }
+
+ c = buf[used++];
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+
+ *sizep = (size_t)size;
+ *usedp = used;
+ return 0;
+}
+
+int git_packfile_unpack_header(
+ size_t *size_p,
+ git_otype *type_p,
+ git_mwindow_file *mwf,
+ git_mwindow **w_curs,
+ git_off_t *curpos)
+{
+ unsigned char *base;
+ unsigned int left;
+ unsigned long used;
+ int ret;
+
+ /* pack_window_open() assures us we have [base, base + 20) available
+ * as a range that we can look at at. (Its actually the hash
+ * size that is assured.) With our object header encoding
+ * the maximum deflated object size is 2^137, which is just
+ * insane, so we know won't exceed what we have been given.
+ */
+/* base = pack_window_open(p, w_curs, *curpos, &left); */
+ base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left);
+ if (base == NULL)
+ return GIT_EBUFS;
+
+ ret = packfile_unpack_header1(&used, size_p, type_p, base, left);
+ git_mwindow_close(w_curs);
+ if (ret == GIT_EBUFS)
+ return ret;
+ else if (ret < 0)
+ return packfile_error("header length is zero");
+
+ *curpos += used;
+ return 0;
+}
+
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
+ struct git_pack_file *p,
+ git_off_t offset)
+{
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = offset;
+ size_t size;
+ git_otype type;
+ git_off_t base_offset;
+ int error;
+
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ if (error < 0)
+ return error;
+
+ if (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ size_t base_size;
+ git_packfile_stream stream;
+
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, offset);
+ git_mwindow_close(&w_curs);
+ if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0)
+ return error;
+ error = git_delta_read_header_fromstream(&base_size, size_p, &stream);
+ git_packfile_stream_free(&stream);
+ if (error < 0)
+ return error;
+ } else {
+ *size_p = size;
+ base_offset = 0;
+ }
+
+ while (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ curpos = base_offset;
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ if (error < 0)
+ return error;
+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)
+ break;
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, base_offset);
+ git_mwindow_close(&w_curs);
+ }
+ *type_p = type;
+
+ return error;
+}
+
+#define SMALL_STACK_SIZE 64
+
+/**
+ * Generate the chain of dependencies which we need to get to the
+ * object at `off`. `chain` is used a stack, popping gives the right
+ * order to apply deltas on. If an object is found in the pack's base
+ * cache, we stop calculating there.
+ */
+static int pack_dependency_chain(git_dependency_chain *chain_out,
+ git_pack_cache_entry **cached_out, git_off_t *cached_off,
+ struct pack_chain_elem *small_stack, size_t *stack_sz,
+ struct git_pack_file *p, git_off_t obj_offset)
+{
+ git_dependency_chain chain = GIT_ARRAY_INIT;
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = obj_offset, base_offset;
+ int error = 0, use_heap = 0;
+ size_t size, elem_pos;
+ git_otype type;
+
+ elem_pos = 0;
+ while (true) {
+ struct pack_chain_elem *elem;
+ git_pack_cache_entry *cached = NULL;
+
+ /* if we have a base cached, we can stop here instead */
+ if ((cached = cache_get(&p->bases, obj_offset)) != NULL) {
+ *cached_out = cached;
+ *cached_off = obj_offset;
+ break;
+ }
+
+ /* if we run out of space on the small stack, use the array */
+ if (elem_pos == SMALL_STACK_SIZE) {
+ git_array_init_to_size(chain, elem_pos);
+ GITERR_CHECK_ARRAY(chain);
+ memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem));
+ chain.size = elem_pos;
+ use_heap = 1;
+ }
+
+ curpos = obj_offset;
+ if (!use_heap) {
+ elem = &small_stack[elem_pos];
+ } else {
+ elem = git_array_alloc(chain);
+ if (!elem) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+ elem->base_key = obj_offset;
+
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+
+ if (error < 0)
+ goto on_error;
+
+ elem->offset = curpos;
+ elem->size = size;
+ elem->type = type;
+ elem->base_key = obj_offset;
+
+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)
+ break;
+
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
+ git_mwindow_close(&w_curs);
+
+ if (base_offset == 0) {
+ error = packfile_error("delta offset is zero");
+ goto on_error;
+ }
+ if (base_offset < 0) { /* must actually be an error code */
+ error = (int)base_offset;
+ goto on_error;
+ }
+
+ /* we need to pass the pos *after* the delta-base bit */
+ elem->offset = curpos;
+
+ /* go through the loop again, but with the new object */
+ obj_offset = base_offset;
+ elem_pos++;
+ }
+
+
+ *stack_sz = elem_pos + 1;
+ *chain_out = chain;
+ return error;
+
+on_error:
+ git_array_clear(chain);
+ return error;
+}
+
+int git_packfile_unpack(
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_off_t *obj_offset)
+{
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = *obj_offset;
+ int error, free_base = 0;
+ git_dependency_chain chain = GIT_ARRAY_INIT;
+ struct pack_chain_elem *elem = NULL, *stack;
+ git_pack_cache_entry *cached = NULL;
+ struct pack_chain_elem small_stack[SMALL_STACK_SIZE];
+ size_t stack_size = 0, elem_pos, alloclen;
+ git_otype base_type;
+
+ /*
+ * TODO: optionally check the CRC on the packfile
+ */
+
+ error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset);
+ if (error < 0)
+ return error;
+
+ obj->data = NULL;
+ obj->len = 0;
+ obj->type = GIT_OBJ_BAD;
+
+ /* let's point to the right stack */
+ stack = chain.ptr ? chain.ptr : small_stack;
+
+ elem_pos = stack_size;
+ if (cached) {
+ memcpy(obj, &cached->raw, sizeof(git_rawobj));
+ base_type = obj->type;
+ elem_pos--; /* stack_size includes the base, which isn't actually there */
+ } else {
+ elem = &stack[--elem_pos];
+ base_type = elem->type;
+ }
+
+ switch (base_type) {
+ case GIT_OBJ_COMMIT:
+ case GIT_OBJ_TREE:
+ case GIT_OBJ_BLOB:
+ case GIT_OBJ_TAG:
+ if (!cached) {
+ curpos = elem->offset;
+ error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type);
+ git_mwindow_close(&w_curs);
+ base_type = elem->type;
+ }
+ if (error < 0)
+ goto cleanup;
+ break;
+ case GIT_OBJ_OFS_DELTA:
+ case GIT_OBJ_REF_DELTA:
+ error = packfile_error("dependency chain ends in a delta");
+ goto cleanup;
+ default:
+ error = packfile_error("invalid packfile type in header");
+ goto cleanup;
+ }
+
+ /*
+ * Finding the object we want a cached base element is
+ * problematic, as we need to make sure we don't accidentally
+ * give the caller the cached object, which it would then feel
+ * free to free, so we need to copy the data.
+ */
+ if (cached && stack_size == 1) {
+ void *data = obj->data;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1);
+ obj->data = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(obj->data);
+
+ memcpy(obj->data, data, obj->len + 1);
+ git_atomic_dec(&cached->refcount);
+ goto cleanup;
+ }
+
+ /* we now apply each consecutive delta until we run out */
+ while (elem_pos > 0 && !error) {
+ git_rawobj base, delta;
+
+ /*
+ * We can now try to add the base to the cache, as
+ * long as it's not already the cached one.
+ */
+ if (!cached)
+ free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key);
+
+ elem = &stack[elem_pos - 1];
+ curpos = elem->offset;
+ error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type);
+ git_mwindow_close(&w_curs);
+
+ if (error < 0)
+ break;
+
+ /* the current object becomes the new base, on which we apply the delta */
+ base = *obj;
+ obj->data = NULL;
+ obj->len = 0;
+ obj->type = GIT_OBJ_BAD;
+
+ error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len);
+ obj->type = base_type;
+
+ /*
+ * We usually don't want to free the base at this
+ * point, as we put it into the cache in the previous
+ * iteration. free_base lets us know that we got the
+ * base object directly from the packfile, so we can free it.
+ */
+ git__free(delta.data);
+ if (free_base) {
+ free_base = 0;
+ git__free(base.data);
+ }
+
+ if (cached) {
+ git_atomic_dec(&cached->refcount);
+ cached = NULL;
+ }
+
+ if (error < 0)
+ break;
+
+ elem_pos--;
+ }
+
+cleanup:
+ if (error < 0) {
+ git__free(obj->data);
+ if (cached)
+ git_atomic_dec(&cached->refcount);
+ }
+
+ if (elem)
+ *obj_offset = curpos;
+
+ git_array_clear(chain);
+ return error;
+}
+
+static void *use_git_alloc(void *opaq, unsigned int count, unsigned int size)
+{
+ GIT_UNUSED(opaq);
+ return git__calloc(count, size);
+}
+
+static void use_git_free(void *opaq, void *ptr)
+{
+ GIT_UNUSED(opaq);
+ git__free(ptr);
+}
+
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos)
+{
+ int st;
+
+ memset(obj, 0, sizeof(git_packfile_stream));
+ obj->curpos = curpos;
+ obj->p = p;
+ obj->zstream.zalloc = use_git_alloc;
+ obj->zstream.zfree = use_git_free;
+ obj->zstream.next_in = Z_NULL;
+ obj->zstream.next_out = Z_NULL;
+ st = inflateInit(&obj->zstream);
+ if (st != Z_OK) {
+ giterr_set(GITERR_ZLIB, "failed to init packfile stream");
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len)
+{
+ unsigned char *in;
+ size_t written;
+ int st;
+
+ if (obj->done)
+ return 0;
+
+ in = pack_window_open(obj->p, &obj->mw, obj->curpos, &obj->zstream.avail_in);
+ if (in == NULL)
+ return GIT_EBUFS;
+
+ obj->zstream.next_out = buffer;
+ obj->zstream.avail_out = (unsigned int)len;
+ obj->zstream.next_in = in;
+
+ st = inflate(&obj->zstream, Z_SYNC_FLUSH);
+ git_mwindow_close(&obj->mw);
+
+ obj->curpos += obj->zstream.next_in - in;
+ written = len - obj->zstream.avail_out;
+
+ if (st != Z_OK && st != Z_STREAM_END) {
+ giterr_set(GITERR_ZLIB, "error reading from the zlib stream");
+ return -1;
+ }
+
+ if (st == Z_STREAM_END)
+ obj->done = 1;
+
+
+ /* If we didn't write anything out but we're not done, we need more data */
+ if (!written && st != Z_STREAM_END)
+ return GIT_EBUFS;
+
+ return written;
+
+}
+
+void git_packfile_stream_free(git_packfile_stream *obj)
+{
+ inflateEnd(&obj->zstream);
+}
+
+static int packfile_unpack_compressed(
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ size_t size,
+ git_otype type)
+{
+ size_t buf_size;
+ int st;
+ z_stream stream;
+ unsigned char *buffer, *in;
+
+ GITERR_CHECK_ALLOC_ADD(&buf_size, size, 1);
+ buffer = git__calloc(1, buf_size);
+ GITERR_CHECK_ALLOC(buffer);
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = buffer;
+ stream.avail_out = (uInt)buf_size;
+ stream.zalloc = use_git_alloc;
+ stream.zfree = use_git_free;
+
+ st = inflateInit(&stream);
+ if (st != Z_OK) {
+ git__free(buffer);
+ giterr_set(GITERR_ZLIB, "failed to init zlib stream on unpack");
+
+ return -1;
+ }
+
+ do {
+ in = pack_window_open(p, w_curs, *curpos, &stream.avail_in);
+ stream.next_in = in;
+ st = inflate(&stream, Z_FINISH);
+ git_mwindow_close(w_curs);
+
+ if (!stream.avail_out)
+ break; /* the payload is larger than it should be */
+
+ if (st == Z_BUF_ERROR && in == NULL) {
+ inflateEnd(&stream);
+ git__free(buffer);
+ return GIT_EBUFS;
+ }
+
+ *curpos += stream.next_in - in;
+ } while (st == Z_OK || st == Z_BUF_ERROR);
+
+ inflateEnd(&stream);
+
+ if ((st != Z_STREAM_END) || stream.total_out != size) {
+ git__free(buffer);
+ giterr_set(GITERR_ZLIB, "error inflating zlib stream");
+ return -1;
+ }
+
+ obj->type = type;
+ obj->len = size;
+ obj->data = buffer;
+ return 0;
+}
+
+/*
+ * curpos is where the data starts, delta_obj_offset is the where the
+ * header starts
+ */
+git_off_t get_delta_base(
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ git_otype type,
+ git_off_t delta_obj_offset)
+{
+ unsigned int left = 0;
+ unsigned char *base_info;
+ git_off_t base_offset;
+ git_oid unused;
+
+ base_info = pack_window_open(p, w_curs, *curpos, &left);
+ /* Assumption: the only reason this would fail is because the file is too small */
+ if (base_info == NULL)
+ return GIT_EBUFS;
+ /* pack_window_open() assured us we have [base_info, base_info + 20)
+ * as a range that we can look at without walking off the
+ * end of the mapped window. Its actually the hash size
+ * that is assured. An OFS_DELTA longer than the hash size
+ * is stupid, as then a REF_DELTA would be smaller to store.
+ */
+ if (type == GIT_OBJ_OFS_DELTA) {
+ unsigned used = 0;
+ unsigned char c = base_info[used++];
+ base_offset = c & 127;
+ while (c & 128) {
+ if (left <= used)
+ return GIT_EBUFS;
+ base_offset += 1;
+ if (!base_offset || MSB(base_offset, 7))
+ return 0; /* overflow */
+ c = base_info[used++];
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = delta_obj_offset - base_offset;
+ if (base_offset <= 0 || base_offset >= delta_obj_offset)
+ return 0; /* out of bound */
+ *curpos += used;
+ } else if (type == GIT_OBJ_REF_DELTA) {
+ /* If we have the cooperative cache, search in it first */
+ if (p->has_cache) {
+ khiter_t k;
+ git_oid oid;
+
+ git_oid_fromraw(&oid, base_info);
+ k = kh_get(oid, p->idx_cache, &oid);
+ if (k != kh_end(p->idx_cache)) {
+ *curpos += 20;
+ return ((struct git_pack_entry *)kh_value(p->idx_cache, k))->offset;
+ } else {
+ /* If we're building an index, don't try to find the pack
+ * entry; we just haven't seen it yet. We'll make
+ * progress again in the next loop.
+ */
+ return GIT_PASSTHROUGH;
+ }
+ }
+
+ /* The base entry _must_ be in the same pack */
+ if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0)
+ return packfile_error("base entry delta is not in the same pack");
+ *curpos += 20;
+ } else
+ return 0;
+
+ return base_offset;
+}
+
+/***********************************************************
+ *
+ * PACKFILE METHODS
+ *
+ ***********************************************************/
+
+void git_packfile_free(struct git_pack_file *p)
+{
+ if (!p)
+ return;
+
+ cache_free(&p->bases);
+
+ if (p->mwf.fd >= 0) {
+ git_mwindow_free_all_locked(&p->mwf);
+ p_close(p->mwf.fd);
+ }
+
+ pack_index_free(p);
+
+ git__free(p->bad_object_sha1);
+
+ git_mutex_free(&p->lock);
+ git_mutex_free(&p->bases.lock);
+ git__free(p);
+}
+
+static int packfile_open(struct git_pack_file *p)
+{
+ struct stat st;
+ struct git_pack_header hdr;
+ git_oid sha1;
+ unsigned char *idx_sha1;
+
+ if (p->index_version == -1 && pack_index_open(p) < 0)
+ return git_odb__error_notfound("failed to open packfile", NULL, 0);
+
+ /* if mwf opened by another thread, return now */
+ if (git_mutex_lock(&p->lock) < 0)
+ return packfile_error("failed to get lock for open");
+
+ if (p->mwf.fd >= 0) {
+ git_mutex_unlock(&p->lock);
+ return 0;
+ }
+
+ /* TODO: open with noatime */
+ p->mwf.fd = git_futils_open_ro(p->pack_name);
+ if (p->mwf.fd < 0)
+ goto cleanup;
+
+ if (p_fstat(p->mwf.fd, &st) < 0 ||
+ git_mwindow_file_register(&p->mwf) < 0)
+ goto cleanup;
+
+ /* If we created the struct before we had the pack we lack size. */
+ if (!p->mwf.size) {
+ if (!S_ISREG(st.st_mode))
+ goto cleanup;
+ p->mwf.size = (git_off_t)st.st_size;
+ } else if (p->mwf.size != st.st_size)
+ goto cleanup;
+
+#if 0
+ /* We leave these file descriptors open with sliding mmap;
+ * there is no point keeping them open across exec(), though.
+ */
+ fd_flag = fcntl(p->mwf.fd, F_GETFD, 0);
+ if (fd_flag < 0)
+ goto cleanup;
+
+ fd_flag |= FD_CLOEXEC;
+ if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1)
+ goto cleanup;
+#endif
+
+ /* Verify we recognize this pack file format. */
+ if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 ||
+ hdr.hdr_signature != htonl(PACK_SIGNATURE) ||
+ !pack_version_ok(hdr.hdr_version))
+ goto cleanup;
+
+ /* Verify the pack matches its index. */
+ if (p->num_objects != ntohl(hdr.hdr_entries) ||
+ p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1 ||
+ p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < 0)
+ goto cleanup;
+
+ idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40;
+
+ if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0)
+ goto cleanup;
+
+ git_mutex_unlock(&p->lock);
+ return 0;
+
+cleanup:
+ giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name);
+
+ if (p->mwf.fd >= 0)
+ p_close(p->mwf.fd);
+ p->mwf.fd = -1;
+
+ git_mutex_unlock(&p->lock);
+
+ return -1;
+}
+
+int git_packfile__name(char **out, const char *path)
+{
+ size_t path_len;
+ git_buf buf = GIT_BUF_INIT;
+
+ path_len = strlen(path);
+
+ if (path_len < strlen(".idx"))
+ return git_odb__error_notfound("invalid packfile path", NULL, 0);
+
+ if (git_buf_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0)
+ return -1;
+
+ *out = git_buf_detach(&buf);
+ return 0;
+}
+
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path)
+{
+ struct stat st;
+ struct git_pack_file *p;
+ size_t path_len = path ? strlen(path) : 0, alloc_len;
+
+ *pack_out = NULL;
+
+ if (path_len < strlen(".idx"))
+ return git_odb__error_notfound("invalid packfile path", NULL, 0);
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*p), path_len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
+
+ p = git__calloc(1, alloc_len);
+ GITERR_CHECK_ALLOC(p);
+
+ memcpy(p->pack_name, path, path_len + 1);
+
+ /*
+ * Make sure a corresponding .pack file exists and that
+ * the index looks sane.
+ */
+ if (git__suffixcmp(path, ".idx") == 0) {
+ size_t root_len = path_len - strlen(".idx");
+
+ memcpy(p->pack_name + root_len, ".keep", sizeof(".keep"));
+ if (git_path_exists(p->pack_name) == true)
+ p->pack_keep = 1;
+
+ memcpy(p->pack_name + root_len, ".pack", sizeof(".pack"));
+ }
+
+ if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) {
+ git__free(p);
+ return git_odb__error_notfound("packfile not found", NULL, 0);
+ }
+
+ /* ok, it looks sane as far as we can check without
+ * actually mapping the pack file.
+ */
+ p->mwf.fd = -1;
+ p->mwf.size = st.st_size;
+ p->pack_local = 1;
+ p->mtime = (git_time_t)st.st_mtime;
+ p->index_version = -1;
+
+ if (git_mutex_init(&p->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize packfile mutex");
+ git__free(p);
+ return -1;
+ }
+
+ if (cache_init(&p->bases) < 0) {
+ git__free(p);
+ return -1;
+ }
+
+ *pack_out = p;
+
+ return 0;
+}
+
+/***********************************************************
+ *
+ * PACKFILE ENTRY SEARCH INTERNALS
+ *
+ ***********************************************************/
+
+static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n)
+{
+ const unsigned char *index = p->index_map.data;
+ const unsigned char *end = index + p->index_map.len;
+ index += 4 * 256;
+ if (p->index_version == 1) {
+ return ntohl(*((uint32_t *)(index + 24 * n)));
+ } else {
+ uint32_t off;
+ index += 8 + p->num_objects * (20 + 4);
+ off = ntohl(*((uint32_t *)(index + 4 * n)));
+ if (!(off & 0x80000000))
+ return off;
+ index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
+
+ /* Make sure we're not being sent out of bounds */
+ if (index >= end - 8)
+ return -1;
+
+ return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) |
+ ntohl(*((uint32_t *)(index + 4)));
+ }
+}
+
+static int git__memcmp4(const void *a, const void *b) {
+ return memcmp(a, b, 4);
+}
+
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data)
+{
+ const unsigned char *index = p->index_map.data, *current;
+ uint32_t i;
+ int error = 0;
+
+ if (index == NULL) {
+ if ((error = pack_index_open(p)) < 0)
+ return error;
+
+ assert(p->index_map.data);
+
+ index = p->index_map.data;
+ }
+
+ if (p->index_version > 1) {
+ index += 8;
+ }
+
+ index += 4 * 256;
+
+ if (p->oids == NULL) {
+ git_vector offsets, oids;
+
+ if ((error = git_vector_init(&oids, p->num_objects, NULL)))
+ return error;
+
+ if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4)))
+ return error;
+
+ if (p->index_version > 1) {
+ const unsigned char *off = index + 24 * p->num_objects;
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&off[4 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)&index[5 * (current - off)]);
+ } else {
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&index[24 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)¤t[4]);
+ }
+
+ git_vector_free(&offsets);
+ p->oids = (git_oid **)git_vector_detach(NULL, NULL, &oids);
+ }
+
+ for (i = 0; i < p->num_objects; i++)
+ if ((error = cb(p->oids[i], data)) != 0)
+ return giterr_set_after_callback(error);
+
+ return error;
+}
+
+static int pack_entry_find_offset(
+ git_off_t *offset_out,
+ git_oid *found_oid,
+ struct git_pack_file *p,
+ const git_oid *short_oid,
+ size_t len)
+{
+ const uint32_t *level1_ofs;
+ const unsigned char *index;
+ unsigned hi, lo, stride;
+ int pos, found = 0;
+ git_off_t offset;
+ const unsigned char *current = 0;
+
+ *offset_out = 0;
+
+ if (p->index_version == -1) {
+ int error;
+
+ if ((error = pack_index_open(p)) < 0)
+ return error;
+ assert(p->index_map.data);
+ }
+
+ index = p->index_map.data;
+ level1_ofs = p->index_map.data;
+
+ if (p->index_version > 1) {
+ level1_ofs += 2;
+ index += 8;
+ }
+
+ index += 4 * 256;
+ hi = ntohl(level1_ofs[(int)short_oid->id[0]]);
+ lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1]));
+
+ if (p->index_version > 1) {
+ stride = 20;
+ } else {
+ stride = 24;
+ index += 4;
+ }
+
+#ifdef INDEX_DEBUG_LOOKUP
+ printf("%02x%02x%02x... lo %u hi %u nr %d\n",
+ short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects);
+#endif
+
+#ifdef GIT_USE_LOOKUP
+ pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id);
+#else
+ pos = sha1_position(index, stride, lo, hi, short_oid->id);
+#endif
+
+ if (pos >= 0) {
+ /* An object matching exactly the oid was found */
+ found = 1;
+ current = index + pos * stride;
+ } else {
+ /* No object was found */
+ /* pos refers to the object with the "closest" oid to short_oid */
+ pos = - 1 - pos;
+ if (pos < (int)p->num_objects) {
+ current = index + pos * stride;
+
+ if (!git_oid_ncmp(short_oid, (const git_oid *)current, len))
+ found = 1;
+ }
+ }
+
+ if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) {
+ /* Check for ambiguousity */
+ const unsigned char *next = current + stride;
+
+ if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) {
+ found = 2;
+ }
+ }
+
+ if (!found)
+ return git_odb__error_notfound("failed to find offset for pack entry", short_oid, len);
+ if (found > 1)
+ return git_odb__error_ambiguous("found multiple offsets for pack entry");
+
+ if ((offset = nth_packed_object_offset(p, pos)) < 0) {
+ giterr_set(GITERR_ODB, "packfile index is corrupt");
+ return -1;
+ }
+
+ *offset_out = offset;
+ git_oid_fromraw(found_oid, current);
+
+#ifdef INDEX_DEBUG_LOOKUP
+ {
+ unsigned char hex_sha1[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(hex_sha1, found_oid);
+ hex_sha1[GIT_OID_HEXSZ] = '\0';
+ printf("found lo=%d %s\n", lo, hex_sha1);
+ }
+#endif
+
+ return 0;
+}
+
+int git_pack_entry_find(
+ struct git_pack_entry *e,
+ struct git_pack_file *p,
+ const git_oid *short_oid,
+ size_t len)
+{
+ git_off_t offset;
+ git_oid found_oid;
+ int error;
+
+ assert(p);
+
+ if (len == GIT_OID_HEXSZ && p->num_bad_objects) {
+ unsigned i;
+ for (i = 0; i < p->num_bad_objects; i++)
+ if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0)
+ return packfile_error("bad object found in packfile");
+ }
+
+ error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len);
+ if (error < 0)
+ return error;
+
+ /* we found a unique entry in the index;
+ * make sure the packfile backing the index
+ * still exists on disk */
+ if (p->mwf.fd == -1 && (error = packfile_open(p)) < 0)
+ return error;
+
+ e->offset = offset;
+ e->p = p;
+
+ git_oid_cpy(&e->sha1, &found_oid);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_pack_h__
+#define INCLUDE_pack_h__
+
+#include <zlib.h>
+
+#include "git2/oid.h"
+
+#include "common.h"
+#include "map.h"
+#include "mwindow.h"
+#include "odb.h"
+#include "oidmap.h"
+#include "array.h"
+
+#define GIT_PACK_FILE_MODE 0444
+
+#define PACK_SIGNATURE 0x5041434b /* "PACK" */
+#define PACK_VERSION 2
+#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3))
+struct git_pack_header {
+ uint32_t hdr_signature;
+ uint32_t hdr_version;
+ uint32_t hdr_entries;
+};
+
+/*
+ * The first four bytes of index formats later than version 1 should
+ * start with this signature, as all older git binaries would find this
+ * value illegal and abort reading the file.
+ *
+ * This is the case because the number of objects in a packfile
+ * cannot exceed 1,431,660,000 as every object would need at least
+ * 3 bytes of data and the overall packfile cannot exceed 4 GiB with
+ * version 1 of the index file due to the offsets limited to 32 bits.
+ * Clearly the signature exceeds this maximum.
+ *
+ * Very old git binaries will also compare the first 4 bytes to the
+ * next 4 bytes in the index and abort with a "non-monotonic index"
+ * error if the second 4 byte word is smaller than the first 4
+ * byte word. This would be true in the proposed future index
+ * format as idx_signature would be greater than idx_version.
+ */
+
+#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */
+
+struct git_pack_idx_header {
+ uint32_t idx_signature;
+ uint32_t idx_version;
+};
+
+typedef struct git_pack_cache_entry {
+ size_t last_usage; /* enough? */
+ git_atomic refcount;
+ git_rawobj raw;
+} git_pack_cache_entry;
+
+struct pack_chain_elem {
+ git_off_t base_key;
+ git_off_t offset;
+ size_t size;
+ git_otype type;
+};
+
+typedef git_array_t(struct pack_chain_elem) git_dependency_chain;
+
+#include "offmap.h"
+#include "oidmap.h"
+
+#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024
+#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */
+
+typedef struct {
+ size_t memory_used;
+ size_t memory_limit;
+ size_t use_ctr;
+ git_mutex lock;
+ git_offmap *entries;
+} git_pack_cache;
+
+struct git_pack_file {
+ git_mwindow_file mwf;
+ git_map index_map;
+ git_mutex lock; /* protect updates to mwf and index_map */
+ git_atomic refcount;
+
+ uint32_t num_objects;
+ uint32_t num_bad_objects;
+ git_oid *bad_object_sha1; /* array of git_oid */
+
+ int index_version;
+ git_time_t mtime;
+ unsigned pack_local:1, pack_keep:1, has_cache:1;
+ git_oidmap *idx_cache;
+ git_oid **oids;
+
+ git_pack_cache bases; /* delta base cache */
+
+ time_t last_freshen; /* last time the packfile was freshened */
+
+ /* something like ".git/objects/pack/xxxxx.pack" */
+ char pack_name[GIT_FLEX_ARRAY]; /* more */
+};
+
+struct git_pack_entry {
+ git_off_t offset;
+ git_oid sha1;
+ struct git_pack_file *p;
+};
+
+typedef struct git_packfile_stream {
+ git_off_t curpos;
+ int done;
+ z_stream zstream;
+ struct git_pack_file *p;
+ git_mwindow *mw;
+} git_packfile_stream;
+
+size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type);
+
+int git_packfile__name(char **out, const char *path);
+
+int git_packfile_unpack_header(
+ size_t *size_p,
+ git_otype *type_p,
+ git_mwindow_file *mwf,
+ git_mwindow **w_curs,
+ git_off_t *curpos);
+
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
+ struct git_pack_file *p,
+ git_off_t offset);
+
+int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset);
+
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos);
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len);
+void git_packfile_stream_free(git_packfile_stream *obj);
+
+git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs,
+ git_off_t *curpos, git_otype type,
+ git_off_t delta_obj_offset);
+
+void git_packfile_free(struct git_pack_file *p);
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path);
+
+int git_pack_entry_find(
+ struct git_pack_entry *e,
+ struct git_pack_file *p,
+ const git_oid *short_oid,
+ size_t len);
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data);
+
+#endif
--- /dev/null
+#include "git2/patch.h"
+#include "diff.h"
+#include "patch.h"
+
+
+int git_patch__invoke_callbacks(
+ git_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload)
+{
+ int error = 0;
+ uint32_t i, j;
+
+ if (file_cb)
+ error = file_cb(patch->delta, 0, payload);
+
+ if (error)
+ return error;
+
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
+ if (binary_cb)
+ error = binary_cb(patch->delta, &patch->binary, payload);
+
+ return error;
+ }
+
+ if (!hunk_cb && !line_cb)
+ return error;
+
+ for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
+ git_patch_hunk *h = git_array_get(patch->hunks, i);
+
+ if (hunk_cb)
+ error = hunk_cb(patch->delta, &h->hunk, payload);
+
+ if (!line_cb)
+ continue;
+
+ for (j = 0; !error && j < h->line_count; ++j) {
+ git_diff_line *l =
+ git_array_get(patch->lines, h->line_start + j);
+
+ error = line_cb(patch->delta, &h->hunk, l, payload);
+ }
+ }
+
+ return error;
+}
+
+size_t git_patch_size(
+ git_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers)
+{
+ size_t out;
+
+ assert(patch);
+
+ out = patch->content_size;
+
+ if (!include_context)
+ out -= patch->context_size;
+
+ if (include_hunk_headers)
+ out += patch->header_size;
+
+ if (include_file_headers) {
+ git_buf file_header = GIT_BUF_INIT;
+
+ if (git_diff_delta__format_file_header(
+ &file_header, patch->delta, NULL, NULL, 0) < 0)
+ giterr_clear();
+ else
+ out += git_buf_len(&file_header);
+
+ git_buf_free(&file_header);
+ }
+
+ return out;
+}
+
+int git_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_patch *patch)
+{
+ size_t totals[3], idx;
+
+ memset(totals, 0, sizeof(totals));
+
+ for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
+ git_diff_line *line = git_array_get(patch->lines, idx);
+ if (!line)
+ continue;
+
+ switch (line->origin) {
+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;
+ default:
+ /* diff --stat and --numstat don't count EOFNL marks because
+ * they will always be paired with a ADDITION or DELETION line.
+ */
+ break;
+ }
+ }
+
+ if (total_ctxt)
+ *total_ctxt = totals[0];
+ if (total_adds)
+ *total_adds = totals[1];
+ if (total_dels)
+ *total_dels = totals[2];
+
+ return 0;
+}
+
+const git_diff_delta *git_patch_get_delta(const git_patch *patch)
+{
+ assert(patch);
+ return patch->delta;
+}
+
+size_t git_patch_num_hunks(const git_patch *patch)
+{
+ assert(patch);
+ return git_array_size(patch->hunks);
+}
+
+static int patch_error_outofrange(const char *thing)
+{
+ giterr_set(GITERR_INVALID, "patch %s index out of range", thing);
+ return GIT_ENOTFOUND;
+}
+
+int git_patch_get_hunk(
+ const git_diff_hunk **out,
+ size_t *lines_in_hunk,
+ git_patch *patch,
+ size_t hunk_idx)
+{
+ git_patch_hunk *hunk;
+ assert(patch);
+
+ hunk = git_array_get(patch->hunks, hunk_idx);
+
+ if (!hunk) {
+ if (out) *out = NULL;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return patch_error_outofrange("hunk");
+ }
+
+ if (out) *out = &hunk->hunk;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
+ return 0;
+}
+
+int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
+{
+ git_patch_hunk *hunk;
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
+ return patch_error_outofrange("hunk");
+ return (int)hunk->line_count;
+}
+
+int git_patch_get_line_in_hunk(
+ const git_diff_line **out,
+ git_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk)
+{
+ git_patch_hunk *hunk;
+ git_diff_line *line;
+
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
+ if (out) *out = NULL;
+ return patch_error_outofrange("hunk");
+ }
+
+ if (line_of_hunk >= hunk->line_count ||
+ !(line = git_array_get(
+ patch->lines, hunk->line_start + line_of_hunk))) {
+ if (out) *out = NULL;
+ return patch_error_outofrange("line");
+ }
+
+ if (out) *out = line;
+ return 0;
+}
+
+int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx)
+{
+ assert(out && diff && diff->patch_fn);
+ return diff->patch_fn(out, diff, idx);
+}
+
+static void git_patch__free(git_patch *patch)
+{
+ if (patch->free_fn)
+ patch->free_fn(patch);
+}
+
+void git_patch_free(git_patch *patch)
+{
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, git_patch__free);
+}
--- /dev/null
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+#ifndef INCLUDE_patch_h__
+#define INCLUDE_patch_h__
+
+#include "git2/patch.h"
+#include "array.h"
+
+/* cached information about a hunk in a patch */
+typedef struct git_patch_hunk {
+ git_diff_hunk hunk;
+ size_t line_start;
+ size_t line_count;
+} git_patch_hunk;
+
+struct git_patch {
+ git_refcount rc;
+
+ git_repository *repo; /* may be null */
+
+ git_diff_options diff_opts;
+
+ git_diff_delta *delta;
+ git_diff_binary binary;
+ git_array_t(git_patch_hunk) hunks;
+ git_array_t(git_diff_line) lines;
+
+ size_t header_size;
+ size_t content_size;
+ size_t context_size;
+
+ void (*free_fn)(git_patch *patch);
+};
+
+extern int git_patch__invoke_callbacks(
+ git_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+extern int git_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_patch *patch);
+
+/** Options for parsing patch files. */
+typedef struct {
+ /**
+ * The length of the prefix (in path segments) for the filenames.
+ * This prefix will be removed when looking for files. The default is 1.
+ */
+ uint32_t prefix_len;
+} git_patch_options;
+
+#define GIT_PATCH_OPTIONS_INIT { 1 }
+
+extern void git_patch_free(git_patch *patch);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/blob.h"
+#include "diff.h"
+#include "diff_generate.h"
+#include "diff_file.h"
+#include "diff_driver.h"
+#include "patch_generate.h"
+#include "diff_xdiff.h"
+#include "delta.h"
+#include "zstream.h"
+#include "fileops.h"
+
+static void diff_output_init(
+ git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
+ git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
+
+static void diff_output_to_patch(
+ git_patch_generated_output *, git_patch_generated *);
+
+static void patch_generated_free(git_patch *p)
+{
+ git_patch_generated *patch = (git_patch_generated *)p;
+
+ git_array_clear(patch->base.lines);
+ git_array_clear(patch->base.hunks);
+
+ git__free((char *)patch->base.binary.old_file.data);
+ git__free((char *)patch->base.binary.new_file.data);
+
+ git_diff_file_content__clear(&patch->ofile);
+ git_diff_file_content__clear(&patch->nfile);
+
+ git_diff_free(patch->diff); /* decrements refcount */
+ patch->diff = NULL;
+
+ git_pool_clear(&patch->flattened);
+
+ git__free((char *)patch->base.diff_opts.old_prefix);
+ git__free((char *)patch->base.diff_opts.new_prefix);
+
+ if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
+ git__free(patch);
+}
+
+static void patch_generated_update_binary(git_patch_generated *patch)
+{
+ if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
+
+ else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
+ patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
+
+ else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
+ (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
+ patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+}
+
+static void patch_generated_init_common(git_patch_generated *patch)
+{
+ patch->base.free_fn = patch_generated_free;
+
+ patch_generated_update_binary(patch);
+
+ patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
+
+ if (patch->diff)
+ git_diff_addref(patch->diff);
+}
+
+static int patch_generated_normalize_options(
+ git_diff_options *out,
+ const git_diff_options *opts)
+{
+ if (opts) {
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
+ memcpy(out, opts, sizeof(git_diff_options));
+ } else {
+ git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT;
+ memcpy(out, &default_opts, sizeof(git_diff_options));
+ }
+
+ out->old_prefix = opts && opts->old_prefix ?
+ git__strdup(opts->old_prefix) :
+ git__strdup(DIFF_OLD_PREFIX_DEFAULT);
+
+ out->new_prefix = opts && opts->new_prefix ?
+ git__strdup(opts->new_prefix) :
+ git__strdup(DIFF_NEW_PREFIX_DEFAULT);
+
+ GITERR_CHECK_ALLOC(out->old_prefix);
+ GITERR_CHECK_ALLOC(out->new_prefix);
+
+ return 0;
+}
+
+static int patch_generated_init(
+ git_patch_generated *patch, git_diff *diff, size_t delta_index)
+{
+ int error = 0;
+
+ memset(patch, 0, sizeof(*patch));
+
+ patch->diff = diff;
+ patch->base.repo = diff->repo;
+ patch->base.delta = git_vector_get(&diff->deltas, delta_index);
+ patch->delta_index = delta_index;
+
+ if ((error = patch_generated_normalize_options(
+ &patch->base.diff_opts, &diff->opts)) < 0 ||
+ (error = git_diff_file_content__init_from_diff(
+ &patch->ofile, diff, patch->base.delta, true)) < 0 ||
+ (error = git_diff_file_content__init_from_diff(
+ &patch->nfile, diff, patch->base.delta, false)) < 0)
+ return error;
+
+ patch_generated_init_common(patch);
+
+ return 0;
+}
+
+static int patch_generated_alloc_from_diff(
+ git_patch_generated **out, git_diff *diff, size_t delta_index)
+{
+ int error;
+ git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
+ GITERR_CHECK_ALLOC(patch);
+
+ if (!(error = patch_generated_init(patch, diff, delta_index))) {
+ patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
+ GIT_REFCOUNT_INC(patch);
+ } else {
+ git__free(patch);
+ patch = NULL;
+ }
+
+ *out = patch;
+ return error;
+}
+
+GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
+{
+ if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
+ return false;
+
+ return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
+}
+
+static bool patch_generated_diffable(git_patch_generated *patch)
+{
+ size_t olen, nlen;
+
+ if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
+ return false;
+
+ /* if we've determined this to be binary (and we are not showing binary
+ * data) then we have skipped loading the map data. instead, query the
+ * file data itself.
+ */
+ if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
+ (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
+ olen = (size_t)patch->ofile.file->size;
+ nlen = (size_t)patch->nfile.file->size;
+ } else {
+ olen = patch->ofile.map.len;
+ nlen = patch->nfile.map.len;
+ }
+
+ /* if both sides are empty, files are identical */
+ if (!olen && !nlen)
+ return false;
+
+ /* otherwise, check the file sizes and the oid */
+ return (olen != nlen ||
+ !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
+}
+
+static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
+{
+ int error = 0;
+ bool incomplete_data;
+
+ if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
+ return 0;
+
+ /* if no hunk and data callbacks and user doesn't care if data looks
+ * binary, then there is no need to actually load the data
+ */
+ if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
+ output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
+ return 0;
+
+ incomplete_data =
+ (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
+ ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
+
+ /* always try to load workdir content first because filtering may
+ * need 2x data size and this minimizes peak memory footprint
+ */
+ if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(
+ &patch->ofile, &patch->base.diff_opts)) < 0 ||
+ should_skip_binary(patch, patch->ofile.file))
+ goto cleanup;
+ }
+ if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(
+ &patch->nfile, &patch->base.diff_opts)) < 0 ||
+ should_skip_binary(patch, patch->nfile.file))
+ goto cleanup;
+ }
+
+ /* once workdir has been tried, load other data as needed */
+ if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(
+ &patch->ofile, &patch->base.diff_opts)) < 0 ||
+ should_skip_binary(patch, patch->ofile.file))
+ goto cleanup;
+ }
+ if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(
+ &patch->nfile, &patch->base.diff_opts)) < 0 ||
+ should_skip_binary(patch, patch->nfile.file))
+ goto cleanup;
+ }
+
+ /* if previously missing an oid, and now that we have it the two sides
+ * are the same (and not submodules), update MODIFIED -> UNMODIFIED
+ */
+ if (incomplete_data &&
+ patch->ofile.file->mode == patch->nfile.file->mode &&
+ patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
+ git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
+ patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
+ patch->base.delta->status = GIT_DELTA_UNMODIFIED;
+
+cleanup:
+ patch_generated_update_binary(patch);
+
+ if (!error) {
+ if (patch_generated_diffable(patch))
+ patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
+
+ patch->flags |= GIT_PATCH_GENERATED_LOADED;
+ }
+
+ return error;
+}
+
+static int patch_generated_invoke_file_callback(
+ git_patch_generated *patch, git_patch_generated_output *output)
+{
+ float progress = patch->diff ?
+ ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
+
+ if (!output->file_cb)
+ return 0;
+
+ return giterr_set_after_callback_function(
+ output->file_cb(patch->base.delta, progress, output->payload),
+ "git_patch");
+}
+
+static int create_binary(
+ git_diff_binary_t *out_type,
+ char **out_data,
+ size_t *out_datalen,
+ size_t *out_inflatedlen,
+ const char *a_data,
+ size_t a_datalen,
+ const char *b_data,
+ size_t b_datalen)
+{
+ git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
+ size_t delta_data_len = 0;
+ int error;
+
+ /* The git_delta function accepts unsigned long only */
+ if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
+ return GIT_EBUFS;
+
+ if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
+ goto done;
+
+ /* The git_delta function accepts unsigned long only */
+ if (!git__is_ulong(deflate.size)) {
+ error = GIT_EBUFS;
+ goto done;
+ }
+
+ if (a_datalen && b_datalen) {
+ void *delta_data;
+
+ error = git_delta(&delta_data, &delta_data_len,
+ a_data, a_datalen,
+ b_data, b_datalen,
+ deflate.size);
+
+ if (error == 0) {
+ error = git_zstream_deflatebuf(
+ &delta, delta_data, delta_data_len);
+
+ git__free(delta_data);
+ } else if (error == GIT_EBUFS) {
+ error = 0;
+ }
+
+ if (error < 0)
+ goto done;
+ }
+
+ if (delta.size && delta.size < deflate.size) {
+ *out_type = GIT_DIFF_BINARY_DELTA;
+ *out_datalen = delta.size;
+ *out_data = git_buf_detach(&delta);
+ *out_inflatedlen = delta_data_len;
+ } else {
+ *out_type = GIT_DIFF_BINARY_LITERAL;
+ *out_datalen = deflate.size;
+ *out_data = git_buf_detach(&deflate);
+ *out_inflatedlen = b_datalen;
+ }
+
+done:
+ git_buf_free(&deflate);
+ git_buf_free(&delta);
+
+ return error;
+}
+
+static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
+{
+ git_diff_binary binary = {0};
+ const char *old_data = patch->ofile.map.data;
+ const char *new_data = patch->nfile.map.data;
+ size_t old_len = patch->ofile.map.len,
+ new_len = patch->nfile.map.len;
+ int error;
+
+ /* Only load contents if the user actually wants to diff
+ * binary files. */
+ if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
+ binary.contains_data = 1;
+
+ /* Create the old->new delta (as the "new" side of the patch),
+ * and the new->old delta (as the "old" side)
+ */
+ if ((error = create_binary(&binary.old_file.type,
+ (char **)&binary.old_file.data,
+ &binary.old_file.datalen,
+ &binary.old_file.inflatedlen,
+ new_data, new_len, old_data, old_len)) < 0 ||
+ (error = create_binary(&binary.new_file.type,
+ (char **)&binary.new_file.data,
+ &binary.new_file.datalen,
+ &binary.new_file.inflatedlen,
+ old_data, old_len, new_data, new_len)) < 0)
+ return error;
+ }
+
+ error = giterr_set_after_callback_function(
+ output->binary_cb(patch->base.delta, &binary, output->payload),
+ "git_patch");
+
+ git__free((char *) binary.old_file.data);
+ git__free((char *) binary.new_file.data);
+
+ return error;
+}
+
+static int patch_generated_create(
+ git_patch_generated *patch,
+ git_patch_generated_output *output)
+{
+ int error = 0;
+
+ if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
+ return 0;
+
+ /* if we are not looking at the binary or text data, don't do the diff */
+ if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
+ return 0;
+
+ if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
+ (error = patch_generated_load(patch, output)) < 0)
+ return error;
+
+ if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
+ return 0;
+
+ if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
+ if (output->binary_cb)
+ error = diff_binary(output, patch);
+ }
+ else {
+ if (output->diff_cb)
+ error = output->diff_cb(output, patch);
+ }
+
+ patch->flags |= GIT_PATCH_GENERATED_DIFFED;
+ return error;
+}
+
+static int diff_required(git_diff *diff, const char *action)
+{
+ if (diff)
+ return 0;
+ giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
+ return -1;
+}
+
+int git_diff_foreach(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ size_t idx;
+ git_patch_generated patch;
+
+ if ((error = diff_required(diff, "git_diff_foreach")) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ memset(&patch, 0, sizeof(patch));
+ diff_output_init(
+ &xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, &diff->opts);
+
+ git_vector_foreach(&diff->deltas, idx, patch.base.delta) {
+
+ /* check flags against patch status */
+ if (git_diff_delta__should_skip(&diff->opts, patch.base.delta))
+ continue;
+
+ if (binary_cb || hunk_cb || data_cb) {
+ if ((error = patch_generated_init(&patch, diff, idx)) != 0 ||
+ (error = patch_generated_load(&patch, &xo.output)) != 0)
+ return error;
+ }
+
+ if ((error = patch_generated_invoke_file_callback(&patch, &xo.output)) == 0) {
+ if (binary_cb || hunk_cb || data_cb)
+ error = patch_generated_create(&patch, &xo.output);
+ }
+
+ git_patch_free(&patch.base);
+
+ if (error)
+ break;
+ }
+
+ return error;
+}
+
+typedef struct {
+ git_patch_generated patch;
+ git_diff_delta delta;
+ char paths[GIT_FLEX_ARRAY];
+} patch_generated_with_delta;
+
+static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
+{
+ int error = 0;
+ git_patch_generated *patch = &pd->patch;
+ bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+ bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+
+ pd->delta.status = has_new ?
+ (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
+ (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
+
+ if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
+ pd->delta.status = GIT_DELTA_UNMODIFIED;
+
+ patch->base.delta = &pd->delta;
+
+ patch_generated_init_common(patch);
+
+ if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
+ !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) {
+
+ /* Even empty patches are flagged as binary, and even though
+ * there's no difference, we flag this as "containing data"
+ * (the data is known to be empty, as opposed to wholly unknown).
+ */
+ if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
+ patch->base.binary.contains_data = 1;
+
+ return error;
+ }
+
+ error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
+
+ if (!error)
+ error = patch_generated_create(patch, (git_patch_generated_output *)xo);
+
+ return error;
+}
+
+static int patch_generated_from_sources(
+ patch_generated_with_delta *pd,
+ git_xdiff_output *xo,
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_repository *repo =
+ oldsrc->blob ? git_blob_owner(oldsrc->blob) :
+ newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
+ git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
+ git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
+
+ if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
+ return error;
+
+ if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
+ void *tmp = lfile; lfile = rfile; rfile = tmp;
+ tmp = ldata; ldata = rdata; rdata = tmp;
+ }
+
+ pd->patch.base.delta = &pd->delta;
+
+ if (!oldsrc->as_path) {
+ if (newsrc->as_path)
+ oldsrc->as_path = newsrc->as_path;
+ else
+ oldsrc->as_path = newsrc->as_path = "file";
+ }
+ else if (!newsrc->as_path)
+ newsrc->as_path = oldsrc->as_path;
+
+ lfile->path = oldsrc->as_path;
+ rfile->path = newsrc->as_path;
+
+ if ((error = git_diff_file_content__init_from_src(
+ ldata, repo, opts, oldsrc, lfile)) < 0 ||
+ (error = git_diff_file_content__init_from_src(
+ rdata, repo, opts, newsrc, rfile)) < 0)
+ return error;
+
+ return diff_single_generate(pd, xo);
+}
+
+static int patch_generated_with_delta_alloc(
+ patch_generated_with_delta **out,
+ const char **old_path,
+ const char **new_path)
+{
+ patch_generated_with_delta *pd;
+ size_t old_len = *old_path ? strlen(*old_path) : 0;
+ size_t new_len = *new_path ? strlen(*new_path) : 0;
+ size_t alloc_len;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
+
+ *out = pd = git__calloc(1, alloc_len);
+ GITERR_CHECK_ALLOC(pd);
+
+ pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
+
+ if (*old_path) {
+ memcpy(&pd->paths[0], *old_path, old_len);
+ *old_path = &pd->paths[0];
+ } else if (*new_path)
+ *old_path = &pd->paths[old_len + 1];
+
+ if (*new_path) {
+ memcpy(&pd->paths[old_len + 1], *new_path, new_len);
+ *new_path = &pd->paths[old_len + 1];
+ } else if (*old_path)
+ *new_path = &pd->paths[0];
+
+ return 0;
+}
+
+static int diff_from_sources(
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ patch_generated_with_delta pd;
+ git_xdiff_output xo;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_init(
+ &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, opts);
+
+ memset(&pd, 0, sizeof(pd));
+
+ error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
+
+ git_patch_free(&pd.patch.base);
+
+ return error;
+}
+
+static int patch_from_sources(
+ git_patch **out,
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ patch_generated_with_delta *pd;
+ git_xdiff_output xo;
+
+ assert(out);
+ *out = NULL;
+
+ if ((error = patch_generated_with_delta_alloc(
+ &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_to_patch(&xo.output, &pd->patch);
+ git_xdiff_init(&xo, opts);
+
+ if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
+ *out = (git_patch *)pd;
+ else
+ git_patch_free((git_patch *)pd);
+
+ return error;
+}
+
+int git_diff_blobs(
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_blobs(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_blob_and_buffer(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_diff_buffers(
+ const void *old_buf,
+ size_t old_len,
+ const char *old_path,
+ const void *new_buf,
+ size_t new_len,
+ const char *new_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_buffers(
+ git_patch **out,
+ const void *old_buf,
+ size_t old_len,
+ const char *old_path,
+ const char *new_buf,
+ size_t new_len,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_patch_generated_from_diff(
+ git_patch **patch_ptr, git_diff *diff, size_t idx)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ git_diff_delta *delta = NULL;
+ git_patch_generated *patch = NULL;
+
+ if (patch_ptr) *patch_ptr = NULL;
+
+ if (diff_required(diff, "git_patch_from_diff") < 0)
+ return -1;
+
+ delta = git_vector_get(&diff->deltas, idx);
+ if (!delta) {
+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
+ return GIT_ENOTFOUND;
+ }
+
+ if (git_diff_delta__should_skip(&diff->opts, delta))
+ return 0;
+
+ /* don't load the patch data unless we need it for binary check */
+ if (!patch_ptr &&
+ ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
+ (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
+ return 0;
+
+ if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_to_patch(&xo.output, patch);
+ git_xdiff_init(&xo, &diff->opts);
+
+ error = patch_generated_invoke_file_callback(patch, &xo.output);
+
+ if (!error)
+ error = patch_generated_create(patch, &xo.output);
+
+ if (!error) {
+ /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
+ /* TODO: and unload the file content */
+ }
+
+ if (error || !patch_ptr)
+ git_patch_free(&patch->base);
+ else
+ *patch_ptr = &patch->base;
+
+ return error;
+}
+
+git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
+{
+ /* ofile driver is representative for whole patch */
+ return patch->ofile.driver;
+}
+
+void git_patch_generated_old_data(
+ char **ptr, size_t *len, git_patch_generated *patch)
+{
+ *ptr = patch->ofile.map.data;
+ *len = patch->ofile.map.len;
+}
+
+void git_patch_generated_new_data(
+ char **ptr, size_t *len, git_patch_generated *patch)
+{
+ *ptr = patch->nfile.map.data;
+ *len = patch->nfile.map.len;
+}
+
+static int patch_generated_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
+ return 0;
+}
+
+static int patch_generated_binary_cb(
+ const git_diff_delta *delta,
+ const git_diff_binary *binary,
+ void *payload)
+{
+ git_patch *patch = payload;
+
+ GIT_UNUSED(delta);
+
+ memcpy(&patch->binary, binary, sizeof(git_diff_binary));
+
+ if (binary->old_file.data) {
+ patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
+ GITERR_CHECK_ALLOC(patch->binary.old_file.data);
+
+ memcpy((char *)patch->binary.old_file.data,
+ binary->old_file.data, binary->old_file.datalen);
+ }
+
+ if (binary->new_file.data) {
+ patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
+ GITERR_CHECK_ALLOC(patch->binary.new_file.data);
+
+ memcpy((char *)patch->binary.new_file.data,
+ binary->new_file.data, binary->new_file.datalen);
+ }
+
+ return 0;
+}
+
+static int git_patch_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk_,
+ void *payload)
+{
+ git_patch_generated *patch = payload;
+ git_patch_hunk *hunk;
+
+ GIT_UNUSED(delta);
+
+ hunk = git_array_alloc(patch->base.hunks);
+ GITERR_CHECK_ALLOC(hunk);
+
+ memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
+
+ patch->base.header_size += hunk_->header_len;
+
+ hunk->line_start = git_array_size(patch->base.lines);
+ hunk->line_count = 0;
+
+ return 0;
+}
+
+static int patch_generated_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk_,
+ const git_diff_line *line_,
+ void *payload)
+{
+ git_patch_generated *patch = payload;
+ git_patch_hunk *hunk;
+ git_diff_line *line;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(hunk_);
+
+ hunk = git_array_last(patch->base.hunks);
+ assert(hunk); /* programmer error if no hunk is available */
+
+ line = git_array_alloc(patch->base.lines);
+ GITERR_CHECK_ALLOC(line);
+
+ memcpy(line, line_, sizeof(*line));
+
+ /* do some bookkeeping so we can provide old/new line numbers */
+
+ patch->base.content_size += line->content_len;
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION)
+ patch->base.content_size += 1;
+ else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
+ patch->base.content_size += 1;
+ patch->base.context_size += line->content_len + 1;
+ } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
+ patch->base.context_size += line->content_len;
+
+ hunk->line_count++;
+
+ return 0;
+}
+
+static void diff_output_init(
+ git_patch_generated_output *out,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ GIT_UNUSED(opts);
+
+ memset(out, 0, sizeof(*out));
+
+ out->file_cb = file_cb;
+ out->binary_cb = binary_cb;
+ out->hunk_cb = hunk_cb;
+ out->data_cb = data_cb;
+ out->payload = payload;
+}
+
+static void diff_output_to_patch(
+ git_patch_generated_output *out, git_patch_generated *patch)
+{
+ diff_output_init(
+ out,
+ NULL,
+ patch_generated_file_cb,
+ patch_generated_binary_cb,
+ git_patch_hunk_cb,
+ patch_generated_line_cb,
+ patch);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_patch_generate_h__
+#define INCLUDE_patch_generate_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "patch.h"
+
+enum {
+ GIT_PATCH_GENERATED_ALLOCATED = (1 << 0),
+ GIT_PATCH_GENERATED_INITIALIZED = (1 << 1),
+ GIT_PATCH_GENERATED_LOADED = (1 << 2),
+ /* the two sides are different */
+ GIT_PATCH_GENERATED_DIFFABLE = (1 << 3),
+ /* the difference between the two sides has been computed */
+ GIT_PATCH_GENERATED_DIFFED = (1 << 4),
+ GIT_PATCH_GENERATED_FLATTENED = (1 << 5),
+};
+
+struct git_patch_generated {
+ struct git_patch base;
+
+ git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ size_t delta_index;
+ git_diff_file_content ofile;
+ git_diff_file_content nfile;
+ uint32_t flags;
+ git_pool flattened;
+};
+
+typedef struct git_patch_generated git_patch_generated;
+
+extern git_diff_driver *git_patch_generated_driver(git_patch_generated *);
+
+extern void git_patch_generated_old_data(
+ char **, size_t *, git_patch_generated *);
+extern void git_patch_generated_new_data(
+ char **, size_t *, git_patch_generated *);
+extern int git_patch_generated_from_diff(
+ git_patch **, git_diff *, size_t);
+
+typedef struct git_patch_generated_output git_patch_generated_output;
+
+struct git_patch_generated_output {
+ /* these callbacks are issued with the diff data */
+ git_diff_file_cb file_cb;
+ git_diff_binary_cb binary_cb;
+ git_diff_hunk_cb hunk_cb;
+ git_diff_line_cb data_cb;
+ void *payload;
+
+ /* this records the actual error in cases where it may be obscured */
+ int error;
+
+ /* this callback is used to do the diff and drive the other callbacks.
+ * see diff_xdiff.h for how to use this in practice for now.
+ */
+ int (*diff_cb)(git_patch_generated_output *output,
+ git_patch_generated *patch);
+};
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/patch.h"
+#include "patch.h"
+#include "patch_parse.h"
+#include "diff_parse.h"
+#include "path.h"
+
+#define parse_err(...) \
+ ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
+
+typedef struct {
+ git_patch base;
+
+ git_patch_parse_ctx *ctx;
+
+ /* the paths from the `diff --git` header, these will be used if this is not
+ * a rename (and rename paths are specified) or if no `+++`/`---` line specify
+ * the paths.
+ */
+ char *header_old_path, *header_new_path;
+
+ /* renamed paths are precise and are not prefixed */
+ char *rename_old_path, *rename_new_path;
+
+ /* the paths given in `---` and `+++` lines */
+ char *old_path, *new_path;
+
+ /* the prefixes from the old/new paths */
+ char *old_prefix, *new_prefix;
+} git_patch_parsed;
+
+
+GIT_INLINE(bool) parse_ctx_contains(
+ git_patch_parse_ctx *ctx, const char *str, size_t len)
+{
+ return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0);
+}
+
+#define parse_ctx_contains_s(ctx, str) \
+ parse_ctx_contains(ctx, str, sizeof(str) - 1)
+
+static void parse_advance_line(git_patch_parse_ctx *ctx)
+{
+ ctx->line += ctx->line_len;
+ ctx->remain_len -= ctx->line_len;
+ ctx->line_len = git__linenlen(ctx->line, ctx->remain_len);
+ ctx->line_num++;
+}
+
+static void parse_advance_chars(git_patch_parse_ctx *ctx, size_t char_cnt)
+{
+ ctx->line += char_cnt;
+ ctx->remain_len -= char_cnt;
+ ctx->line_len -= char_cnt;
+}
+
+static int parse_advance_expected(
+ git_patch_parse_ctx *ctx,
+ const char *expected,
+ size_t expected_len)
+{
+ if (ctx->line_len < expected_len)
+ return -1;
+
+ if (memcmp(ctx->line, expected, expected_len) != 0)
+ return -1;
+
+ parse_advance_chars(ctx, expected_len);
+ return 0;
+}
+
+#define parse_advance_expected_str(ctx, str) \
+ parse_advance_expected(ctx, str, strlen(str))
+
+static int parse_advance_ws(git_patch_parse_ctx *ctx)
+{
+ int ret = -1;
+
+ while (ctx->line_len > 0 &&
+ ctx->line[0] != '\n' &&
+ git__isspace(ctx->line[0])) {
+ ctx->line++;
+ ctx->line_len--;
+ ctx->remain_len--;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int parse_advance_nl(git_patch_parse_ctx *ctx)
+{
+ if (ctx->line_len != 1 || ctx->line[0] != '\n')
+ return -1;
+
+ parse_advance_line(ctx);
+ return 0;
+}
+
+static int header_path_len(git_patch_parse_ctx *ctx)
+{
+ bool inquote = 0;
+ bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"');
+ size_t len;
+
+ for (len = quoted; len < ctx->line_len; len++) {
+ if (!quoted && git__isspace(ctx->line[len]))
+ break;
+ else if (quoted && !inquote && ctx->line[len] == '"') {
+ len++;
+ break;
+ }
+
+ inquote = (!inquote && ctx->line[len] == '\\');
+ }
+
+ return len;
+}
+
+static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx)
+{
+ int path_len, error = 0;
+
+ path_len = header_path_len(ctx);
+
+ if ((error = git_buf_put(path, ctx->line, path_len)) < 0)
+ goto done;
+
+ parse_advance_chars(ctx, path_len);
+
+ git_buf_rtrim(path);
+
+ if (path->size > 0 && path->ptr[0] == '"')
+ error = git_buf_unquote(path);
+
+ if (error < 0)
+ goto done;
+
+ git_path_squash_slashes(path);
+
+done:
+ return error;
+}
+
+static int parse_header_path(char **out, git_patch_parse_ctx *ctx)
+{
+ git_buf path = GIT_BUF_INIT;
+ int error = parse_header_path_buf(&path, ctx);
+
+ *out = git_buf_detach(&path);
+
+ return error;
+}
+
+static int parse_header_git_oldpath(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ return parse_header_path(&patch->old_path, ctx);
+}
+
+static int parse_header_git_newpath(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ return parse_header_path(&patch->new_path, ctx);
+}
+
+static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx)
+{
+ const char *end;
+ int32_t m;
+ int ret;
+
+ if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]))
+ return parse_err("invalid file mode at line %"PRIuZ, ctx->line_num);
+
+ if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0)
+ return ret;
+
+ if (m > UINT16_MAX)
+ return -1;
+
+ *mode = (uint16_t)m;
+
+ parse_advance_chars(ctx, (end - ctx->line));
+
+ return ret;
+}
+
+static int parse_header_oid(
+ git_oid *oid,
+ uint16_t *oid_len,
+ git_patch_parse_ctx *ctx)
+{
+ size_t len;
+
+ for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) {
+ if (!git__isxdigit(ctx->line[len]))
+ break;
+ }
+
+ if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ ||
+ git_oid_fromstrn(oid, ctx->line, len) < 0)
+ return parse_err("invalid hex formatted object id at line %"PRIuZ,
+ ctx->line_num);
+
+ parse_advance_chars(ctx, len);
+
+ *oid_len = (uint16_t)len;
+
+ return 0;
+}
+
+static int parse_header_git_index(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ if (parse_header_oid(&patch->base.delta->old_file.id,
+ &patch->base.delta->old_file.id_abbrev, ctx) < 0 ||
+ parse_advance_expected_str(ctx, "..") < 0 ||
+ parse_header_oid(&patch->base.delta->new_file.id,
+ &patch->base.delta->new_file.id_abbrev, ctx) < 0)
+ return -1;
+
+ if (ctx->line_len > 0 && ctx->line[0] == ' ') {
+ uint16_t mode;
+
+ parse_advance_chars(ctx, 1);
+
+ if (parse_header_mode(&mode, ctx) < 0)
+ return -1;
+
+ if (!patch->base.delta->new_file.mode)
+ patch->base.delta->new_file.mode = mode;
+
+ if (!patch->base.delta->old_file.mode)
+ patch->base.delta->old_file.mode = mode;
+ }
+
+ return 0;
+}
+
+static int parse_header_git_oldmode(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
+}
+
+static int parse_header_git_newmode(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
+}
+
+static int parse_header_git_deletedfilemode(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ git__free((char *)patch->base.delta->old_file.path);
+
+ patch->base.delta->old_file.path = NULL;
+ patch->base.delta->status = GIT_DELTA_DELETED;
+ patch->base.delta->nfiles = 1;
+
+ return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
+}
+
+static int parse_header_git_newfilemode(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ git__free((char *)patch->base.delta->new_file.path);
+
+ patch->base.delta->new_file.path = NULL;
+ patch->base.delta->status = GIT_DELTA_ADDED;
+ patch->base.delta->nfiles = 1;
+
+ return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
+}
+
+static int parse_header_rename(
+ char **out,
+ git_patch_parse_ctx *ctx)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ if (parse_header_path_buf(&path, ctx) < 0)
+ return -1;
+
+ /* Note: the `rename from` and `rename to` lines include the literal
+ * filename. They do *not* include the prefix. (Who needs consistency?)
+ */
+ *out = git_buf_detach(&path);
+ return 0;
+}
+
+static int parse_header_renamefrom(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ patch->base.delta->status = GIT_DELTA_RENAMED;
+ return parse_header_rename(&patch->rename_old_path, ctx);
+}
+
+static int parse_header_renameto(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ patch->base.delta->status = GIT_DELTA_RENAMED;
+ return parse_header_rename(&patch->rename_new_path, ctx);
+}
+
+static int parse_header_copyfrom(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ patch->base.delta->status = GIT_DELTA_COPIED;
+ return parse_header_rename(&patch->rename_old_path, ctx);
+}
+
+static int parse_header_copyto(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ patch->base.delta->status = GIT_DELTA_COPIED;
+ return parse_header_rename(&patch->rename_new_path, ctx);
+}
+
+static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx)
+{
+ int32_t val;
+ const char *end;
+
+ if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) ||
+ git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0)
+ return -1;
+
+ parse_advance_chars(ctx, (end - ctx->line));
+
+ if (parse_advance_expected_str(ctx, "%") < 0)
+ return -1;
+
+ if (val > 100)
+ return -1;
+
+ *out = val;
+ return 0;
+}
+
+static int parse_header_similarity(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0)
+ return parse_err("invalid similarity percentage at line %"PRIuZ,
+ ctx->line_num);
+
+ return 0;
+}
+
+static int parse_header_dissimilarity(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ uint16_t dissimilarity;
+
+ if (parse_header_percent(&dissimilarity, ctx) < 0)
+ return parse_err("invalid similarity percentage at line %"PRIuZ,
+ ctx->line_num);
+
+ patch->base.delta->similarity = 100 - dissimilarity;
+
+ return 0;
+}
+
+typedef struct {
+ const char *str;
+ int(*fn)(git_patch_parsed *, git_patch_parse_ctx *);
+} header_git_op;
+
+static const header_git_op header_git_ops[] = {
+ { "diff --git ", NULL },
+ { "@@ -", NULL },
+ { "GIT binary patch", NULL },
+ { "Binary files ", NULL },
+ { "--- ", parse_header_git_oldpath },
+ { "+++ ", parse_header_git_newpath },
+ { "index ", parse_header_git_index },
+ { "old mode ", parse_header_git_oldmode },
+ { "new mode ", parse_header_git_newmode },
+ { "deleted file mode ", parse_header_git_deletedfilemode },
+ { "new file mode ", parse_header_git_newfilemode },
+ { "rename from ", parse_header_renamefrom },
+ { "rename to ", parse_header_renameto },
+ { "rename old ", parse_header_renamefrom },
+ { "rename new ", parse_header_renameto },
+ { "copy from ", parse_header_copyfrom },
+ { "copy to ", parse_header_copyto },
+ { "similarity index ", parse_header_similarity },
+ { "dissimilarity index ", parse_header_dissimilarity },
+};
+
+static int parse_header_git(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ size_t i;
+ int error = 0;
+
+ /* Parse the diff --git line */
+ if (parse_advance_expected_str(ctx, "diff --git ") < 0)
+ return parse_err("corrupt git diff header at line %"PRIuZ, ctx->line_num);
+
+ if (parse_header_path(&patch->header_old_path, ctx) < 0)
+ return parse_err("corrupt old path in git diff header at line %"PRIuZ,
+ ctx->line_num);
+
+ if (parse_advance_ws(ctx) < 0 ||
+ parse_header_path(&patch->header_new_path, ctx) < 0)
+ return parse_err("corrupt new path in git diff header at line %"PRIuZ,
+ ctx->line_num);
+
+ /* Parse remaining header lines */
+ for (parse_advance_line(ctx);
+ ctx->remain_len > 0;
+ parse_advance_line(ctx)) {
+
+ bool found = false;
+
+ if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n')
+ break;
+
+ for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) {
+ const header_git_op *op = &header_git_ops[i];
+ size_t op_len = strlen(op->str);
+
+ if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0)
+ continue;
+
+ /* Do not advance if this is the patch separator */
+ if (op->fn == NULL)
+ goto done;
+
+ parse_advance_chars(ctx, op_len);
+
+ if ((error = op->fn(patch, ctx)) < 0)
+ goto done;
+
+ parse_advance_ws(ctx);
+ parse_advance_expected_str(ctx, "\n");
+
+ if (ctx->line_len > 0) {
+ error = parse_err("trailing data at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ error = parse_err("invalid patch header at line %"PRIuZ,
+ ctx->line_num);
+ goto done;
+ }
+ }
+
+done:
+ return error;
+}
+
+static int parse_number(git_off_t *out, git_patch_parse_ctx *ctx)
+{
+ const char *end;
+ int64_t num;
+
+ if (!git__isdigit(ctx->line[0]))
+ return -1;
+
+ if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0)
+ return -1;
+
+ if (num < 0)
+ return -1;
+
+ *out = num;
+ parse_advance_chars(ctx, (end - ctx->line));
+
+ return 0;
+}
+
+static int parse_int(int *out, git_patch_parse_ctx *ctx)
+{
+ git_off_t num;
+
+ if (parse_number(&num, ctx) < 0 || !git__is_int(num))
+ return -1;
+
+ *out = (int)num;
+ return 0;
+}
+
+static int parse_hunk_header(
+ git_patch_hunk *hunk,
+ git_patch_parse_ctx *ctx)
+{
+ const char *header_start = ctx->line;
+
+ hunk->hunk.old_lines = 1;
+ hunk->hunk.new_lines = 1;
+
+ if (parse_advance_expected_str(ctx, "@@ -") < 0 ||
+ parse_int(&hunk->hunk.old_start, ctx) < 0)
+ goto fail;
+
+ if (ctx->line_len > 0 && ctx->line[0] == ',') {
+ if (parse_advance_expected_str(ctx, ",") < 0 ||
+ parse_int(&hunk->hunk.old_lines, ctx) < 0)
+ goto fail;
+ }
+
+ if (parse_advance_expected_str(ctx, " +") < 0 ||
+ parse_int(&hunk->hunk.new_start, ctx) < 0)
+ goto fail;
+
+ if (ctx->line_len > 0 && ctx->line[0] == ',') {
+ if (parse_advance_expected_str(ctx, ",") < 0 ||
+ parse_int(&hunk->hunk.new_lines, ctx) < 0)
+ goto fail;
+ }
+
+ if (parse_advance_expected_str(ctx, " @@") < 0)
+ goto fail;
+
+ parse_advance_line(ctx);
+
+ if (!hunk->hunk.old_lines && !hunk->hunk.new_lines)
+ goto fail;
+
+ hunk->hunk.header_len = ctx->line - header_start;
+ if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1))
+ return parse_err("oversized patch hunk header at line %"PRIuZ,
+ ctx->line_num);
+
+ memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len);
+ hunk->hunk.header[hunk->hunk.header_len] = '\0';
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_PATCH, "invalid patch hunk header at line %"PRIuZ,
+ ctx->line_num);
+ return -1;
+}
+
+static int parse_hunk_body(
+ git_patch_parsed *patch,
+ git_patch_hunk *hunk,
+ git_patch_parse_ctx *ctx)
+{
+ git_diff_line *line;
+ int error = 0;
+
+ int oldlines = hunk->hunk.old_lines;
+ int newlines = hunk->hunk.new_lines;
+
+ for (;
+ ctx->remain_len > 4 && (oldlines || newlines) &&
+ memcmp(ctx->line, "@@ -", 4) != 0;
+ parse_advance_line(ctx)) {
+
+ int origin;
+ int prefix = 1;
+
+ if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') {
+ error = parse_err("invalid patch instruction at line %"PRIuZ,
+ ctx->line_num);
+ goto done;
+ }
+
+ switch (ctx->line[0]) {
+ case '\n':
+ prefix = 0;
+
+ case ' ':
+ origin = GIT_DIFF_LINE_CONTEXT;
+ oldlines--;
+ newlines--;
+ break;
+
+ case '-':
+ origin = GIT_DIFF_LINE_DELETION;
+ oldlines--;
+ break;
+
+ case '+':
+ origin = GIT_DIFF_LINE_ADDITION;
+ newlines--;
+ break;
+
+ default:
+ error = parse_err("invalid patch hunk at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ line = git_array_alloc(patch->base.lines);
+ GITERR_CHECK_ALLOC(line);
+
+ memset(line, 0x0, sizeof(git_diff_line));
+
+ line->content = ctx->line + prefix;
+ line->content_len = ctx->line_len - prefix;
+ line->content_offset = ctx->content_len - ctx->remain_len;
+ line->origin = origin;
+
+ hunk->line_count++;
+ }
+
+ if (oldlines || newlines) {
+ error = parse_err(
+ "invalid patch hunk, expected %d old lines and %d new lines",
+ hunk->hunk.old_lines, hunk->hunk.new_lines);
+ goto done;
+ }
+
+ /* Handle "\ No newline at end of file". Only expect the leading
+ * backslash, though, because the rest of the string could be
+ * localized. Because `diff` optimizes for the case where you
+ * want to apply the patch by hand.
+ */
+ if (parse_ctx_contains_s(ctx, "\\ ") &&
+ git_array_size(patch->base.lines) > 0) {
+
+ line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1);
+
+ if (line->content_len < 1) {
+ error = parse_err("cannot trim trailing newline of empty line");
+ goto done;
+ }
+
+ line->content_len--;
+
+ parse_advance_line(ctx);
+ }
+
+done:
+ return error;
+}
+
+static int parse_patch_header(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ int error = 0;
+
+ for (ctx->line = ctx->remain;
+ ctx->remain_len > 0;
+ parse_advance_line(ctx)) {
+
+ /* This line is too short to be a patch header. */
+ if (ctx->line_len < 6)
+ continue;
+
+ /* This might be a hunk header without a patch header, provide a
+ * sensible error message. */
+ if (parse_ctx_contains_s(ctx, "@@ -")) {
+ size_t line_num = ctx->line_num;
+ git_patch_hunk hunk;
+
+ /* If this cannot be parsed as a hunk header, it's just leading
+ * noise, continue.
+ */
+ if (parse_hunk_header(&hunk, ctx) < 0) {
+ giterr_clear();
+ continue;
+ }
+
+ error = parse_err("invalid hunk header outside patch at line %"PRIuZ,
+ line_num);
+ goto done;
+ }
+
+ /* This buffer is too short to contain a patch. */
+ if (ctx->remain_len < ctx->line_len + 6)
+ break;
+
+ /* A proper git patch */
+ if (parse_ctx_contains_s(ctx, "diff --git ")) {
+ error = parse_header_git(patch, ctx);
+ goto done;
+ }
+
+ error = 0;
+ continue;
+ }
+
+ giterr_set(GITERR_PATCH, "no patch found");
+ error = GIT_ENOTFOUND;
+
+done:
+ return error;
+}
+
+static int parse_patch_binary_side(
+ git_diff_binary_file *binary,
+ git_patch_parse_ctx *ctx)
+{
+ git_diff_binary_t type = GIT_DIFF_BINARY_NONE;
+ git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT;
+ git_off_t len;
+ int error = 0;
+
+ if (parse_ctx_contains_s(ctx, "literal ")) {
+ type = GIT_DIFF_BINARY_LITERAL;
+ parse_advance_chars(ctx, 8);
+ } else if (parse_ctx_contains_s(ctx, "delta ")) {
+ type = GIT_DIFF_BINARY_DELTA;
+ parse_advance_chars(ctx, 6);
+ } else {
+ error = parse_err(
+ "unknown binary delta type at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) {
+ error = parse_err("invalid binary size at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ while (ctx->line_len) {
+ char c = ctx->line[0];
+ size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size;
+
+ if (c == '\n')
+ break;
+ else if (c >= 'A' && c <= 'Z')
+ decoded_len = c - 'A' + 1;
+ else if (c >= 'a' && c <= 'z')
+ decoded_len = c - 'a' + (('z' - 'a') + 1) + 1;
+
+ if (!decoded_len) {
+ error = parse_err("invalid binary length at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ parse_advance_chars(ctx, 1);
+
+ encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
+
+ if (encoded_len > ctx->line_len - 1) {
+ error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ if ((error = git_buf_decode_base85(
+ &decoded, ctx->line, encoded_len, decoded_len)) < 0)
+ goto done;
+
+ if (decoded.size - decoded_orig != decoded_len) {
+ error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
+ parse_advance_chars(ctx, encoded_len);
+
+ if (parse_advance_nl(ctx) < 0) {
+ error = parse_err("trailing data at line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+ }
+
+ binary->type = type;
+ binary->inflatedlen = (size_t)len;
+ binary->datalen = decoded.size;
+ binary->data = git_buf_detach(&decoded);
+
+done:
+ git_buf_free(&base85);
+ git_buf_free(&decoded);
+ return error;
+}
+
+static int parse_patch_binary(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ int error;
+
+ if (parse_advance_expected_str(ctx, "GIT binary patch") < 0 ||
+ parse_advance_nl(ctx) < 0)
+ return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num);
+
+ /* parse old->new binary diff */
+ if ((error = parse_patch_binary_side(
+ &patch->base.binary.new_file, ctx)) < 0)
+ return error;
+
+ if (parse_advance_nl(ctx) < 0)
+ return parse_err("corrupt git binary separator at line %"PRIuZ,
+ ctx->line_num);
+
+ /* parse new->old binary diff */
+ if ((error = parse_patch_binary_side(
+ &patch->base.binary.old_file, ctx)) < 0)
+ return error;
+
+ if (parse_advance_nl(ctx) < 0)
+ return parse_err("corrupt git binary patch separator at line %"PRIuZ,
+ ctx->line_num);
+
+ patch->base.binary.contains_data = 1;
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
+ return 0;
+}
+
+static int parse_patch_binary_nodata(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ if (parse_advance_expected_str(ctx, "Binary files ") < 0 ||
+ parse_advance_expected_str(ctx, patch->header_old_path) < 0 ||
+ parse_advance_expected_str(ctx, " and ") < 0 ||
+ parse_advance_expected_str(ctx, patch->header_new_path) < 0 ||
+ parse_advance_expected_str(ctx, " differ") < 0 ||
+ parse_advance_nl(ctx) < 0)
+ return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num);
+
+ patch->base.binary.contains_data = 0;
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
+ return 0;
+}
+
+static int parse_patch_hunks(
+ git_patch_parsed *patch,
+ git_patch_parse_ctx *ctx)
+{
+ git_patch_hunk *hunk;
+ int error = 0;
+
+ while (parse_ctx_contains_s(ctx, "@@ -")) {
+ hunk = git_array_alloc(patch->base.hunks);
+ GITERR_CHECK_ALLOC(hunk);
+
+ memset(hunk, 0, sizeof(git_patch_hunk));
+
+ hunk->line_start = git_array_size(patch->base.lines);
+ hunk->line_count = 0;
+
+ if ((error = parse_hunk_header(hunk, ctx)) < 0 ||
+ (error = parse_hunk_body(patch, hunk, ctx)) < 0)
+ goto done;
+ }
+
+ patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+
+done:
+ return error;
+}
+
+static int parse_patch_body(
+ git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ if (parse_ctx_contains_s(ctx, "GIT binary patch"))
+ return parse_patch_binary(patch, ctx);
+ else if (parse_ctx_contains_s(ctx, "Binary files "))
+ return parse_patch_binary_nodata(patch, ctx);
+ else
+ return parse_patch_hunks(patch, ctx);
+}
+
+int check_header_names(
+ const char *one,
+ const char *two,
+ const char *old_or_new,
+ bool two_null)
+{
+ if (!one || !two)
+ return 0;
+
+ if (two_null && strcmp(two, "/dev/null") != 0)
+ return parse_err("expected %s path of '/dev/null'", old_or_new);
+
+ else if (!two_null && strcmp(one, two) != 0)
+ return parse_err("mismatched %s path names", old_or_new);
+
+ return 0;
+}
+
+static int check_prefix(
+ char **out,
+ size_t *out_len,
+ git_patch_parsed *patch,
+ const char *path_start)
+{
+ const char *path = path_start;
+ size_t prefix_len = patch->ctx->opts.prefix_len;
+ size_t remain_len = prefix_len;
+
+ *out = NULL;
+ *out_len = 0;
+
+ if (prefix_len == 0)
+ goto done;
+
+ /* leading slashes do not count as part of the prefix in git apply */
+ while (*path == '/')
+ path++;
+
+ while (*path && remain_len) {
+ if (*path == '/')
+ remain_len--;
+
+ path++;
+ }
+
+ if (remain_len || !*path)
+ return parse_err(
+ "header filename does not contain %"PRIuZ" path components",
+ prefix_len);
+
+done:
+ *out_len = (path - path_start);
+ *out = git__strndup(path_start, *out_len);
+
+ return (*out == NULL) ? -1 : 0;
+}
+
+static int check_filenames(git_patch_parsed *patch)
+{
+ const char *prefixed_new, *prefixed_old;
+ size_t old_prefixlen = 0, new_prefixlen = 0;
+ bool added = (patch->base.delta->status == GIT_DELTA_ADDED);
+ bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED);
+
+ if (patch->old_path && !patch->new_path)
+ return parse_err("missing new path");
+
+ if (!patch->old_path && patch->new_path)
+ return parse_err("missing old path");
+
+ /* Ensure (non-renamed) paths match */
+ if (check_header_names(
+ patch->header_old_path, patch->old_path, "old", added) < 0 ||
+ check_header_names(
+ patch->header_new_path, patch->new_path, "new", deleted) < 0)
+ return -1;
+
+ prefixed_old = (!added && patch->old_path) ? patch->old_path :
+ patch->header_old_path;
+ prefixed_new = (!deleted && patch->new_path) ? patch->new_path :
+ patch->header_new_path;
+
+ if (check_prefix(
+ &patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0 ||
+ check_prefix(
+ &patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)
+ return -1;
+
+ /* Prefer the rename filenames as they are unambiguous and unprefixed */
+ if (patch->rename_old_path)
+ patch->base.delta->old_file.path = patch->rename_old_path;
+ else
+ patch->base.delta->old_file.path = prefixed_old + old_prefixlen;
+
+ if (patch->rename_new_path)
+ patch->base.delta->new_file.path = patch->rename_new_path;
+ else
+ patch->base.delta->new_file.path = prefixed_new + new_prefixlen;
+
+ if (!patch->base.delta->old_file.path &&
+ !patch->base.delta->new_file.path)
+ return parse_err("git diff header lacks old / new paths");
+
+ return 0;
+}
+
+static int check_patch(git_patch_parsed *patch)
+{
+ git_diff_delta *delta = patch->base.delta;
+
+ if (check_filenames(patch) < 0)
+ return -1;
+
+ if (delta->old_file.path &&
+ delta->status != GIT_DELTA_DELETED &&
+ !delta->new_file.mode)
+ delta->new_file.mode = delta->old_file.mode;
+
+ if (delta->status == GIT_DELTA_MODIFIED &&
+ !(delta->flags & GIT_DIFF_FLAG_BINARY) &&
+ delta->new_file.mode == delta->old_file.mode &&
+ git_array_size(patch->base.hunks) == 0)
+ return parse_err("patch with no hunks");
+
+ if (delta->status == GIT_DELTA_ADDED) {
+ memset(&delta->old_file.id, 0x0, sizeof(git_oid));
+ delta->old_file.id_abbrev = 0;
+ }
+
+ if (delta->status == GIT_DELTA_DELETED) {
+ memset(&delta->new_file.id, 0x0, sizeof(git_oid));
+ delta->new_file.id_abbrev = 0;
+ }
+
+ return 0;
+}
+
+git_patch_parse_ctx *git_patch_parse_ctx_init(
+ const char *content,
+ size_t content_len,
+ const git_patch_options *opts)
+{
+ git_patch_parse_ctx *ctx;
+ git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT;
+
+ if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL)
+ return NULL;
+
+ if (content_len) {
+ if ((ctx->content = git__malloc(content_len)) == NULL) {
+ git__free(ctx);
+ return NULL;
+ }
+
+ memcpy((char *)ctx->content, content, content_len);
+ }
+
+ ctx->content_len = content_len;
+ ctx->remain = ctx->content;
+ ctx->remain_len = ctx->content_len;
+
+ if (opts)
+ memcpy(&ctx->opts, opts, sizeof(git_patch_options));
+ else
+ memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options));
+
+ GIT_REFCOUNT_INC(ctx);
+ return ctx;
+}
+
+static void patch_parse_ctx_free(git_patch_parse_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ git__free((char *)ctx->content);
+ git__free(ctx);
+}
+
+void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx)
+{
+ GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free);
+}
+
+int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx)
+{
+ git_diff_parsed *diff = (git_diff_parsed *)d;
+ git_patch *p;
+
+ if ((p = git_vector_get(&diff->patches, idx)) == NULL)
+ return -1;
+
+ GIT_REFCOUNT_INC(p);
+ *out = p;
+
+ return 0;
+}
+
+static void patch_parsed__free(git_patch *p)
+{
+ git_patch_parsed *patch = (git_patch_parsed *)p;
+
+ if (!patch)
+ return;
+
+ git_patch_parse_ctx_free(patch->ctx);
+
+ git__free((char *)patch->base.binary.old_file.data);
+ git__free((char *)patch->base.binary.new_file.data);
+ git_array_clear(patch->base.hunks);
+ git_array_clear(patch->base.lines);
+ git__free(patch->base.delta);
+
+ git__free(patch->old_prefix);
+ git__free(patch->new_prefix);
+ git__free(patch->header_old_path);
+ git__free(patch->header_new_path);
+ git__free(patch->rename_old_path);
+ git__free(patch->rename_new_path);
+ git__free(patch->old_path);
+ git__free(patch->new_path);
+ git__free(patch);
+}
+
+int git_patch_parse(
+ git_patch **out,
+ git_patch_parse_ctx *ctx)
+{
+ git_patch_parsed *patch;
+ size_t start, used;
+ int error = 0;
+
+ assert(out && ctx);
+
+ *out = NULL;
+
+ patch = git__calloc(1, sizeof(git_patch_parsed));
+ GITERR_CHECK_ALLOC(patch);
+
+ patch->ctx = ctx;
+ GIT_REFCOUNT_INC(patch->ctx);
+
+ patch->base.free_fn = patch_parsed__free;
+
+ patch->base.delta = git__calloc(1, sizeof(git_diff_delta));
+ GITERR_CHECK_ALLOC(patch->base.delta);
+
+ patch->base.delta->status = GIT_DELTA_MODIFIED;
+ patch->base.delta->nfiles = 2;
+
+ start = ctx->remain_len;
+
+ if ((error = parse_patch_header(patch, ctx)) < 0 ||
+ (error = parse_patch_body(patch, ctx)) < 0 ||
+ (error = check_patch(patch)) < 0)
+ goto done;
+
+ used = start - ctx->remain_len;
+ ctx->remain += used;
+
+ patch->base.diff_opts.old_prefix = patch->old_prefix;
+ patch->base.diff_opts.new_prefix = patch->new_prefix;
+ patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY;
+
+ GIT_REFCOUNT_INC(patch);
+ *out = &patch->base;
+
+done:
+ if (error < 0)
+ patch_parsed__free(&patch->base);
+
+ return error;
+}
+
+int git_patch_from_buffer(
+ git_patch **out,
+ const char *content,
+ size_t content_len,
+ const git_patch_options *opts)
+{
+ git_patch_parse_ctx *ctx;
+ int error;
+
+ ctx = git_patch_parse_ctx_init(content, content_len, opts);
+ GITERR_CHECK_ALLOC(ctx);
+
+ error = git_patch_parse(out, ctx);
+
+ git_patch_parse_ctx_free(ctx);
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_patch_parse_h__
+#define INCLUDE_patch_parse_h__
+
+typedef struct {
+ git_refcount rc;
+
+ /* Original content buffer */
+ const char *content;
+ size_t content_len;
+
+ git_patch_options opts;
+
+ /* The remaining (unparsed) buffer */
+ const char *remain;
+ size_t remain_len;
+
+ const char *line;
+ size_t line_len;
+ size_t line_num;
+} git_patch_parse_ctx;
+
+extern git_patch_parse_ctx *git_patch_parse_ctx_init(
+ const char *content,
+ size_t content_len,
+ const git_patch_options *opts);
+
+extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx);
+
+/**
+ * Create a patch for a single file from the contents of a patch buffer.
+ *
+ * @param out The patch to be created
+ * @param contents The contents of a patch file
+ * @param contents_len The length of the patch file
+ * @param opts The git_patch_options
+ * @return 0 on success, <0 on failure.
+ */
+extern int git_patch_from_buffer(
+ git_patch **out,
+ const char *contents,
+ size_t contents_len,
+ const git_patch_options *opts);
+
+extern int git_patch_parse(
+ git_patch **out,
+ git_patch_parse_ctx *ctx);
+
+extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "path.h"
+#include "posix.h"
+#include "repository.h"
+#ifdef GIT_WIN32
+#include "win32/posix.h"
+#include "win32/w32_buffer.h"
+#include "win32/w32_util.h"
+#include "win32/version.h"
+#else
+#include <dirent.h>
+#endif
+#include <stdio.h>
+#include <ctype.h>
+
+#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+
+#ifdef GIT_WIN32
+static bool looks_like_network_computer_name(const char *path, int pos)
+{
+ if (pos < 3)
+ return false;
+
+ if (path[0] != '/' || path[1] != '/')
+ return false;
+
+ while (pos-- > 2) {
+ if (path[pos] == '/')
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+/*
+ * Based on the Android implementation, BSD licensed.
+ * http://android.git.kernel.org/
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+int git_path_basename_r(git_buf *buffer, const char *path)
+{
+ const char *endp, *startp;
+ int len, result;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ startp = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* All slashes becomes "/" */
+ if (endp == path && *endp == '/') {
+ startp = "/";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Find the start of the base */
+ startp = endp;
+ while (startp > path && *(startp - 1) != '/')
+ startp--;
+
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - startp + 1);
+
+Exit:
+ result = len;
+
+ if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
+ return -1;
+
+ return result;
+}
+
+/*
+ * Based on the Android implementation, BSD licensed.
+ * Check http://android.git.kernel.org/
+ */
+int git_path_dirname_r(git_buf *buffer, const char *path)
+{
+ const char *endp;
+ int result, len;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ path = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* Find the start of the dir */
+ while (endp > path && *endp != '/')
+ endp--;
+
+ /* Either the dir is "/" or there are no slashes */
+ if (endp == path) {
+ path = (*endp == '/') ? "/" : ".";
+ len = 1;
+ goto Exit;
+ }
+
+ do {
+ endp--;
+ } while (endp > path && *endp == '/');
+
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - path + 1);
+
+#ifdef GIT_WIN32
+ /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
+ 'C:/' here */
+
+ if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
+ len = 3;
+ goto Exit;
+ }
+
+ /* Similarly checks if we're dealing with a network computer name
+ '//computername/.git' will return '//computername/' */
+
+ if (looks_like_network_computer_name(path, len)) {
+ len++;
+ goto Exit;
+ }
+
+#endif
+
+Exit:
+ result = len;
+
+ if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
+ return -1;
+
+ return result;
+}
+
+
+char *git_path_dirname(const char *path)
+{
+ git_buf buf = GIT_BUF_INIT;
+ char *dirname;
+
+ git_path_dirname_r(&buf, path);
+ dirname = git_buf_detach(&buf);
+ git_buf_free(&buf); /* avoid memleak if error occurs */
+
+ return dirname;
+}
+
+char *git_path_basename(const char *path)
+{
+ git_buf buf = GIT_BUF_INIT;
+ char *basename;
+
+ git_path_basename_r(&buf, path);
+ basename = git_buf_detach(&buf);
+ git_buf_free(&buf); /* avoid memleak if error occurs */
+
+ return basename;
+}
+
+size_t git_path_basename_offset(git_buf *buffer)
+{
+ ssize_t slash;
+
+ if (!buffer || buffer->size <= 0)
+ return 0;
+
+ slash = git_buf_rfind_next(buffer, '/');
+
+ if (slash >= 0 && buffer->ptr[slash] == '/')
+ return (size_t)(slash + 1);
+
+ return 0;
+}
+
+const char *git_path_topdir(const char *path)
+{
+ size_t len;
+ ssize_t i;
+
+ assert(path);
+ len = strlen(path);
+
+ if (!len || path[len - 1] != '/')
+ return NULL;
+
+ for (i = (ssize_t)len - 2; i >= 0; --i)
+ if (path[i] == '/')
+ break;
+
+ return &path[i + 1];
+}
+
+int git_path_root(const char *path)
+{
+ int offset = 0;
+
+ /* Does the root of the path look like a windows drive ? */
+ if (LOOKS_LIKE_DRIVE_PREFIX(path))
+ offset += 2;
+
+#ifdef GIT_WIN32
+ /* Are we dealing with a windows network path? */
+ else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
+ (path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
+ {
+ offset += 2;
+
+ /* Skip the computer name segment */
+ while (path[offset] && path[offset] != '/' && path[offset] != '\\')
+ offset++;
+ }
+#endif
+
+ if (path[offset] == '/' || path[offset] == '\\')
+ return offset;
+
+ return -1; /* Not a real error - signals that path is not rooted */
+}
+
+void git_path_trim_slashes(git_buf *path)
+{
+ int ceiling = git_path_root(path->ptr) + 1;
+ assert(ceiling >= 0);
+
+ while (path->size > (size_t)ceiling) {
+ if (path->ptr[path->size-1] != '/')
+ break;
+
+ path->ptr[path->size-1] = '\0';
+ path->size--;
+ }
+}
+
+int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
+{
+ ssize_t root;
+
+ assert(path && path_out);
+
+ root = (ssize_t)git_path_root(path);
+
+ if (base != NULL && root < 0) {
+ if (git_buf_joinpath(path_out, base, path) < 0)
+ return -1;
+
+ root = (ssize_t)strlen(base);
+ } else {
+ if (git_buf_sets(path_out, path) < 0)
+ return -1;
+
+ if (root < 0)
+ root = 0;
+ else if (base)
+ git_path_equal_or_prefixed(base, path, &root);
+ }
+
+ if (root_at)
+ *root_at = root;
+
+ return 0;
+}
+
+void git_path_squash_slashes(git_buf *path)
+{
+ char *p, *q;
+
+ if (path->size == 0)
+ return;
+
+ for (p = path->ptr, q = path->ptr; *q; p++, q++) {
+ *p = *q;
+
+ while (*q == '/' && *(q+1) == '/') {
+ path->size--;
+ q++;
+ }
+ }
+
+ *p = '\0';
+}
+
+int git_path_prettify(git_buf *path_out, const char *path, const char *base)
+{
+ char buf[GIT_PATH_MAX];
+
+ assert(path && path_out);
+
+ /* construct path if needed */
+ if (base != NULL && git_path_root(path) < 0) {
+ if (git_buf_joinpath(path_out, base, path) < 0)
+ return -1;
+ path = path_out->ptr;
+ }
+
+ if (p_realpath(path, buf) == NULL) {
+ /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
+ int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
+ giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
+
+ git_buf_clear(path_out);
+
+ return error;
+ }
+
+ return git_buf_sets(path_out, buf);
+}
+
+int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
+{
+ int error = git_path_prettify(path_out, path, base);
+ return (error < 0) ? error : git_path_to_dir(path_out);
+}
+
+int git_path_to_dir(git_buf *path)
+{
+ if (path->asize > 0 &&
+ git_buf_len(path) > 0 &&
+ path->ptr[git_buf_len(path) - 1] != '/')
+ git_buf_putc(path, '/');
+
+ return git_buf_oom(path) ? -1 : 0;
+}
+
+void git_path_string_to_dir(char* path, size_t size)
+{
+ size_t end = strlen(path);
+
+ if (end && path[end - 1] != '/' && end < size) {
+ path[end] = '/';
+ path[end + 1] = '\0';
+ }
+}
+
+int git__percent_decode(git_buf *decoded_out, const char *input)
+{
+ int len, hi, lo, i;
+ assert(decoded_out && input);
+
+ len = (int)strlen(input);
+ git_buf_clear(decoded_out);
+
+ for(i = 0; i < len; i++)
+ {
+ char c = input[i];
+
+ if (c != '%')
+ goto append;
+
+ if (i >= len - 2)
+ goto append;
+
+ hi = git__fromhex(input[i + 1]);
+ lo = git__fromhex(input[i + 2]);
+
+ if (hi < 0 || lo < 0)
+ goto append;
+
+ c = (char)(hi << 4 | lo);
+ i += 2;
+
+append:
+ if (git_buf_putc(decoded_out, c) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int error_invalid_local_file_uri(const char *uri)
+{
+ giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri);
+ return -1;
+}
+
+static int local_file_url_prefixlen(const char *file_url)
+{
+ int len = -1;
+
+ if (git__prefixcmp(file_url, "file://") == 0) {
+ if (file_url[7] == '/')
+ len = 8;
+ else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
+ len = 17;
+ }
+
+ return len;
+}
+
+bool git_path_is_local_file_url(const char *file_url)
+{
+ return (local_file_url_prefixlen(file_url) > 0);
+}
+
+int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+{
+ int offset;
+
+ assert(local_path_out && file_url);
+
+ if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
+ file_url[offset] == '\0' || file_url[offset] == '/')
+ return error_invalid_local_file_uri(file_url);
+
+#ifndef GIT_WIN32
+ offset--; /* A *nix absolute path starts with a forward slash */
+#endif
+
+ git_buf_clear(local_path_out);
+ return git__percent_decode(local_path_out, file_url + offset);
+}
+
+int git_path_walk_up(
+ git_buf *path,
+ const char *ceiling,
+ int (*cb)(void *data, const char *),
+ void *data)
+{
+ int error = 0;
+ git_buf iter;
+ ssize_t stop = 0, scan;
+ char oldc = '\0';
+
+ assert(path && cb);
+
+ if (ceiling != NULL) {
+ if (git__prefixcmp(path->ptr, ceiling) == 0)
+ stop = (ssize_t)strlen(ceiling);
+ else
+ stop = git_buf_len(path);
+ }
+ scan = git_buf_len(path);
+
+ /* empty path: yield only once */
+ if (!scan) {
+ error = cb(data, "");
+ if (error)
+ giterr_set_after_callback(error);
+ return error;
+ }
+
+ iter.ptr = path->ptr;
+ iter.size = git_buf_len(path);
+ iter.asize = path->asize;
+
+ while (scan >= stop) {
+ error = cb(data, iter.ptr);
+ iter.ptr[scan] = oldc;
+
+ if (error) {
+ giterr_set_after_callback(error);
+ break;
+ }
+
+ scan = git_buf_rfind_next(&iter, '/');
+ if (scan >= 0) {
+ scan++;
+ oldc = iter.ptr[scan];
+ iter.size = scan;
+ iter.ptr[scan] = '\0';
+ }
+ }
+
+ if (scan >= 0)
+ iter.ptr[scan] = oldc;
+
+ /* relative path: yield for the last component */
+ if (!error && stop == 0 && iter.ptr[0] != '/') {
+ error = cb(data, "");
+ if (error)
+ giterr_set_after_callback(error);
+ }
+
+ return error;
+}
+
+bool git_path_exists(const char *path)
+{
+ assert(path);
+ return p_access(path, F_OK) == 0;
+}
+
+bool git_path_isdir(const char *path)
+{
+ struct stat st;
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISDIR(st.st_mode) != 0;
+}
+
+bool git_path_isfile(const char *path)
+{
+ struct stat st;
+
+ assert(path);
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISREG(st.st_mode) != 0;
+}
+
+bool git_path_islink(const char *path)
+{
+ struct stat st;
+
+ assert(path);
+ if (p_lstat(path, &st) < 0)
+ return false;
+
+ return S_ISLNK(st.st_mode) != 0;
+}
+
+#ifdef GIT_WIN32
+
+bool git_path_is_empty_dir(const char *path)
+{
+ git_win32_path filter_w;
+ bool empty = false;
+
+ if (git_win32__findfirstfile_filter(filter_w, path)) {
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(filter_w, &findData);
+
+ /* FindFirstFile will fail if there are no children to the given
+ * path, which can happen if the given path is a file (and obviously
+ * has no children) or if the given path is an empty mount point.
+ * (Most directories have at least directory entries '.' and '..',
+ * but ridiculously another volume mounted in another drive letter's
+ * path space do not, and thus have nothing to enumerate.) If
+ * FindFirstFile fails, check if this is a directory-like thing
+ * (a mount point).
+ */
+ if (hFind == INVALID_HANDLE_VALUE)
+ return git_path_isdir(path);
+
+ /* If the find handle was created successfully, then it's a directory */
+ empty = true;
+
+ do {
+ /* Allow the enumeration to return . and .. and still be considered
+ * empty. In the special case of drive roots (i.e. C:\) where . and
+ * .. do not occur, we can still consider the path to be an empty
+ * directory if there's nothing there. */
+ if (!git_path_is_dot_or_dotdotW(findData.cFileName)) {
+ empty = false;
+ break;
+ }
+ } while (FindNextFileW(hFind, &findData));
+
+ FindClose(hFind);
+ }
+
+ return empty;
+}
+
+#else
+
+static int path_found_entry(void *payload, git_buf *path)
+{
+ GIT_UNUSED(payload);
+ return !git_path_is_dot_or_dotdot(path->ptr);
+}
+
+bool git_path_is_empty_dir(const char *path)
+{
+ int error;
+ git_buf dir = GIT_BUF_INIT;
+
+ if (!git_path_isdir(path))
+ return false;
+
+ if ((error = git_buf_sets(&dir, path)) != 0)
+ giterr_clear();
+ else
+ error = git_path_direach(&dir, 0, path_found_entry, NULL);
+
+ git_buf_free(&dir);
+
+ return !error;
+}
+
+#endif
+
+int git_path_set_error(int errno_value, const char *path, const char *action)
+{
+ switch (errno_value) {
+ case ENOENT:
+ case ENOTDIR:
+ giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action);
+ return GIT_ENOTFOUND;
+
+ case EINVAL:
+ case ENAMETOOLONG:
+ giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path);
+ return GIT_EINVALIDSPEC;
+
+ case EEXIST:
+ giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
+ return GIT_EEXISTS;
+
+ case EACCES:
+ giterr_set(GITERR_OS, "Failed %s - '%s' is locked", action, path);
+ return GIT_ELOCKED;
+
+ default:
+ giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
+ return -1;
+ }
+}
+
+int git_path_lstat(const char *path, struct stat *st)
+{
+ if (p_lstat(path, st) == 0)
+ return 0;
+
+ return git_path_set_error(errno, path, "stat");
+}
+
+static bool _check_dir_contents(
+ git_buf *dir,
+ const char *sub,
+ bool (*predicate)(const char *))
+{
+ bool result;
+ size_t dir_size = git_buf_len(dir);
+ size_t sub_size = strlen(sub);
+ size_t alloc_size;
+
+ /* leave base valid even if we could not make space for subdir */
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
+ GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
+ git_buf_try_grow(dir, alloc_size, false) < 0)
+ return false;
+
+ /* save excursion */
+ git_buf_joinpath(dir, dir->ptr, sub);
+
+ result = predicate(dir->ptr);
+
+ /* restore path */
+ git_buf_truncate(dir, dir_size);
+ return result;
+}
+
+bool git_path_contains(git_buf *dir, const char *item)
+{
+ return _check_dir_contents(dir, item, &git_path_exists);
+}
+
+bool git_path_contains_dir(git_buf *base, const char *subdir)
+{
+ return _check_dir_contents(base, subdir, &git_path_isdir);
+}
+
+bool git_path_contains_file(git_buf *base, const char *file)
+{
+ return _check_dir_contents(base, file, &git_path_isfile);
+}
+
+int git_path_find_dir(git_buf *dir, const char *path, const char *base)
+{
+ int error = git_path_join_unrooted(dir, path, base, NULL);
+
+ if (!error) {
+ char buf[GIT_PATH_MAX];
+ if (p_realpath(dir->ptr, buf) != NULL)
+ error = git_buf_sets(dir, buf);
+ }
+
+ /* call dirname if this is not a directory */
+ if (!error) /* && git_path_isdir(dir->ptr) == false) */
+ error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
+
+ if (!error)
+ error = git_path_to_dir(dir);
+
+ return error;
+}
+
+int git_path_resolve_relative(git_buf *path, size_t ceiling)
+{
+ char *base, *to, *from, *next;
+ size_t len;
+
+ GITERR_CHECK_ALLOC_BUF(path);
+
+ if (ceiling > path->size)
+ ceiling = path->size;
+
+ /* recognize drive prefixes, etc. that should not be backed over */
+ if (ceiling == 0)
+ ceiling = git_path_root(path->ptr) + 1;
+
+ /* recognize URL prefixes that should not be backed over */
+ if (ceiling == 0) {
+ for (next = path->ptr; *next && git__isalpha(*next); ++next);
+ if (next[0] == ':' && next[1] == '/' && next[2] == '/')
+ ceiling = (next + 3) - path->ptr;
+ }
+
+ base = to = from = path->ptr + ceiling;
+
+ while (*from) {
+ for (next = from; *next && *next != '/'; ++next);
+
+ len = next - from;
+
+ if (len == 1 && from[0] == '.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == '.' && from[1] == '.') {
+ /* error out if trying to up one from a hard base */
+ if (to == base && ceiling != 0) {
+ giterr_set(GITERR_INVALID,
+ "Cannot strip root component off url");
+ return -1;
+ }
+
+ /* no more path segments to strip,
+ * use '../' as a new base path */
+ if (to == base) {
+ if (*next == '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ /* this is now the base, can't back up from a
+ * relative prefix */
+ base = to;
+ } else {
+ /* back up a path segment */
+ while (to > base && to[-1] == '/') to--;
+ while (to > base && to[-1] != '/') to--;
+ }
+ } else {
+ if (*next == '/' && *from != '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == '/') from++;
+ }
+
+ *to = '\0';
+
+ path->size = to - path->ptr;
+
+ return 0;
+}
+
+int git_path_apply_relative(git_buf *target, const char *relpath)
+{
+ git_buf_joinpath(target, git_buf_cstr(target), relpath);
+ return git_path_resolve_relative(target, 0);
+}
+
+int git_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t))
+{
+ unsigned char c1, c2;
+ size_t len = len1 < len2 ? len1 : len2;
+ int cmp;
+
+ cmp = compare(name1, name2, len);
+ if (cmp)
+ return cmp;
+
+ c1 = name1[len];
+ c2 = name2[len];
+
+ if (c1 == '\0' && isdir1)
+ c1 = '/';
+
+ if (c2 == '\0' && isdir2)
+ c2 = '/';
+
+ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+size_t git_path_common_dirlen(const char *one, const char *two)
+{
+ const char *p, *q, *dirsep = NULL;
+
+ for (p = one, q = two; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/')
+ dirsep = p;
+ else if (*p != *q)
+ break;
+ }
+
+ return dirsep ? (dirsep - one) + 1 : 0;
+}
+
+int git_path_make_relative(git_buf *path, const char *parent)
+{
+ const char *p, *q, *p_dirsep, *q_dirsep;
+ size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
+
+ for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/') {
+ p_dirsep = p;
+ q_dirsep = q;
+ }
+ else if (*p != *q)
+ break;
+ }
+
+ /* need at least 1 common path segment */
+ if ((p_dirsep == path->ptr || q_dirsep == parent) &&
+ (*p_dirsep != '/' || *q_dirsep != '/')) {
+ giterr_set(GITERR_INVALID,
+ "%s is not a parent of %s", parent, path->ptr);
+ return GIT_ENOTFOUND;
+ }
+
+ if (*p == '/' && !*q)
+ p++;
+ else if (!*p && *q == '/')
+ q++;
+ else if (!*p && !*q)
+ return git_buf_clear(path), 0;
+ else {
+ p = p_dirsep + 1;
+ q = q_dirsep + 1;
+ }
+
+ plen -= (p - path->ptr);
+
+ if (!*q)
+ return git_buf_set(path, p, plen);
+
+ for (; (q = strchr(q, '/')) && *(q + 1); q++)
+ depth++;
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
+ GITERR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
+
+ /* save the offset as we might realllocate the pointer */
+ offset = p - path->ptr;
+ if (git_buf_try_grow(path, alloclen, 1) < 0)
+ return -1;
+ p = path->ptr + offset;
+
+ memmove(path->ptr + (depth * 3), p, plen + 1);
+
+ for (i = 0; i < depth; i++)
+ memcpy(path->ptr + (i * 3), "../", 3);
+
+ path->size = newlen;
+ return 0;
+}
+
+bool git_path_has_non_ascii(const char *path, size_t pathlen)
+{
+ const uint8_t *scan = (const uint8_t *)path, *end;
+
+ for (end = scan + pathlen; scan < end; ++scan)
+ if (*scan & 0x80)
+ return true;
+
+ return false;
+}
+
+#ifdef GIT_USE_ICONV
+
+int git_path_iconv_init_precompose(git_path_iconv_t *ic)
+{
+ git_buf_init(&ic->buf, 0);
+ ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
+ return 0;
+}
+
+void git_path_iconv_clear(git_path_iconv_t *ic)
+{
+ if (ic) {
+ if (ic->map != (iconv_t)-1)
+ iconv_close(ic->map);
+ git_buf_free(&ic->buf);
+ }
+}
+
+int git_path_iconv(git_path_iconv_t *ic, const char **in, size_t *inlen)
+{
+ char *nfd = (char*)*in, *nfc;
+ size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv;
+ int retry = 1;
+
+ if (!ic || ic->map == (iconv_t)-1 ||
+ !git_path_has_non_ascii(*in, *inlen))
+ return 0;
+
+ git_buf_clear(&ic->buf);
+
+ while (1) {
+ GITERR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
+ if (git_buf_grow(&ic->buf, alloclen) < 0)
+ return -1;
+
+ nfc = ic->buf.ptr + ic->buf.size;
+ nfclen = ic->buf.asize - ic->buf.size;
+
+ rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
+
+ ic->buf.size = (nfc - ic->buf.ptr);
+
+ if (rv != (size_t)-1)
+ break;
+
+ /* if we cannot convert the data (probably because iconv thinks
+ * it is not valid UTF-8 source data), then use original data
+ */
+ if (errno != E2BIG)
+ return 0;
+
+ /* make space for 2x the remaining data to be converted
+ * (with per retry overhead to avoid infinite loops)
+ */
+ wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
+
+ if (retry++ > 4)
+ goto fail;
+ }
+
+ ic->buf.ptr[ic->buf.size] = '\0';
+
+ *in = ic->buf.ptr;
+ *inlen = ic->buf.size;
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_OS, "Unable to convert unicode path data");
+ return -1;
+}
+
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
+
+/* Check if the platform is decomposing unicode data for us. We will
+ * emulate core Git and prefer to use precomposed unicode data internally
+ * on these platforms, composing the decomposed unicode on the fly.
+ *
+ * This mainly happens on the Mac where HDFS stores filenames as
+ * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
+ * return decomposed unicode from readdir() even when the actual
+ * filesystem is storing precomposed unicode.
+ */
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ bool found_decomposed = false;
+ char tmp[6];
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0 ||
+ (fd = p_mkstemp(path.ptr)) < 0)
+ goto done;
+ p_close(fd);
+
+ /* record trailing digits generated by mkstemp */
+ memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
+
+ /* try to look up as NFD path */
+ if (git_buf_joinpath(&path, root, nfd_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ found_decomposed = git_path_exists(path.ptr);
+
+ /* remove temporary file (using original precomposed path) */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ (void)p_unlink(path.ptr);
+
+done:
+ git_buf_free(&path);
+ return found_decomposed;
+}
+
+#else
+
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ GIT_UNUSED(root);
+ return false;
+}
+
+#endif
+
+#if defined(__sun) || defined(__GNU__)
+typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
+#else
+typedef struct dirent path_dirent_data;
+#endif
+
+int git_path_direach(
+ git_buf *path,
+ uint32_t flags,
+ int (*fn)(void *, git_buf *),
+ void *arg)
+{
+ int error = 0;
+ ssize_t wd_len;
+ DIR *dir;
+ struct dirent *de;
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
+
+ GIT_UNUSED(flags);
+
+ if (git_path_to_dir(path) < 0)
+ return -1;
+
+ wd_len = git_buf_len(path);
+
+ if ((dir = opendir(path->ptr)) == NULL) {
+ giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
+ if (errno == ENOENT)
+ return GIT_ENOTFOUND;
+
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&ic);
+#endif
+
+ while ((de = readdir(dir)) != NULL) {
+ const char *de_path = de->d_name;
+ size_t de_len = strlen(de_path);
+
+ if (git_path_is_dot_or_dotdot(de_path))
+ continue;
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
+ break;
+#endif
+
+ if ((error = git_buf_put(path, de_path, de_len)) < 0)
+ break;
+
+ giterr_clear();
+ error = fn(arg, path);
+
+ git_buf_truncate(path, wd_len); /* restore path */
+
+ /* Only set our own error if the callback did not set one already */
+ if (error != 0) {
+ if (!giterr_last())
+ giterr_set_after_callback(error);
+
+ break;
+ }
+ }
+
+ closedir(dir);
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
+
+ return error;
+}
+
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
+ * and better.
+ */
+#ifndef FIND_FIRST_EX_LARGE_FETCH
+# define FIND_FIRST_EX_LARGE_FETCH 2
+#endif
+
+int git_path_diriter_init(
+ git_path_diriter *diriter,
+ const char *path,
+ unsigned int flags)
+{
+ git_win32_path path_filter;
+
+ static int is_win7_or_later = -1;
+ if (is_win7_or_later < 0)
+ is_win7_or_later = git_has_win32_version(6, 1, 0);
+
+ assert(diriter && path);
+
+ memset(diriter, 0, sizeof(git_path_diriter));
+ diriter->handle = INVALID_HANDLE_VALUE;
+
+ if (git_buf_puts(&diriter->path_utf8, path) < 0)
+ return -1;
+
+ git_path_trim_slashes(&diriter->path_utf8);
+
+ if (diriter->path_utf8.size == 0) {
+ giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+ return -1;
+ }
+
+ if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
+ !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
+ giterr_set(GITERR_OS, "Could not parse the directory path '%s'", path);
+ return -1;
+ }
+
+ diriter->handle = FindFirstFileExW(
+ path_filter,
+ is_win7_or_later ? FindExInfoBasic : FindExInfoStandard,
+ &diriter->current,
+ FindExSearchNameMatch,
+ NULL,
+ is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
+
+ if (diriter->handle == INVALID_HANDLE_VALUE) {
+ giterr_set(GITERR_OS, "Could not open directory '%s'", path);
+ return -1;
+ }
+
+ diriter->parent_utf8_len = diriter->path_utf8.size;
+ diriter->flags = flags;
+ return 0;
+}
+
+static int diriter_update_paths(git_path_diriter *diriter)
+{
+ size_t filename_len, path_len;
+
+ filename_len = wcslen(diriter->current.cFileName);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
+ return -1;
+
+ if (path_len > GIT_WIN_PATH_UTF16) {
+ giterr_set(GITERR_FILESYSTEM,
+ "invalid path '%.*ls\\%ls' (path too long)",
+ diriter->parent_len, diriter->path, diriter->current.cFileName);
+ return -1;
+ }
+
+ diriter->path[diriter->parent_len] = L'\\';
+ memcpy(&diriter->path[diriter->parent_len+1],
+ diriter->current.cFileName, filename_len * sizeof(wchar_t));
+ diriter->path[path_len-1] = L'\0';
+
+ git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
+
+ if (diriter->parent_utf8_len > 0 &&
+ diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/')
+ git_buf_putc(&diriter->path_utf8, '/');
+
+ git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
+
+ if (git_buf_oom(&diriter->path_utf8))
+ return -1;
+
+ return 0;
+}
+
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+ bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+
+ do {
+ /* Our first time through, we already have the data from
+ * FindFirstFileW. Use it, otherwise get the next file.
+ */
+ if (!diriter->needs_next)
+ diriter->needs_next = 1;
+ else if (!FindNextFileW(diriter->handle, &diriter->current))
+ return GIT_ITEROVER;
+ } while (skip_dot && git_path_is_dot_or_dotdotW(diriter->current.cFileName));
+
+ if (diriter_update_paths(diriter) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ assert(diriter->path_utf8.size > diriter->parent_utf8_len);
+
+ *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
+ *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
+ return 0;
+}
+
+int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ *out = diriter->path_utf8.ptr;
+ *out_len = diriter->path_utf8.size;
+ return 0;
+}
+
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
+{
+ assert(out && diriter);
+
+ return git_win32__file_attribute_to_stat(out,
+ (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
+ diriter->path);
+}
+
+void git_path_diriter_free(git_path_diriter *diriter)
+{
+ if (diriter == NULL)
+ return;
+
+ git_buf_free(&diriter->path_utf8);
+
+ if (diriter->handle != INVALID_HANDLE_VALUE) {
+ FindClose(diriter->handle);
+ diriter->handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+#else
+
+int git_path_diriter_init(
+ git_path_diriter *diriter,
+ const char *path,
+ unsigned int flags)
+{
+ assert(diriter && path);
+
+ memset(diriter, 0, sizeof(git_path_diriter));
+
+ if (git_buf_puts(&diriter->path, path) < 0)
+ return -1;
+
+ git_path_trim_slashes(&diriter->path);
+
+ if (diriter->path.size == 0) {
+ giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+ return -1;
+ }
+
+ if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
+ git_buf_free(&diriter->path);
+
+ giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&diriter->ic);
+#endif
+
+ diriter->parent_len = diriter->path.size;
+ diriter->flags = flags;
+
+ return 0;
+}
+
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+ struct dirent *de;
+ const char *filename;
+ size_t filename_len;
+ bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+ int error = 0;
+
+ assert(diriter);
+
+ errno = 0;
+
+ do {
+ if ((de = readdir(diriter->dir)) == NULL) {
+ if (!errno)
+ return GIT_ITEROVER;
+
+ giterr_set(GITERR_OS,
+ "Could not read directory '%s'", diriter->path.ptr);
+ return -1;
+ }
+ } while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
+
+ filename = de->d_name;
+ filename_len = strlen(filename);
+
+#ifdef GIT_USE_ICONV
+ if ((diriter->flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
+ (error = git_path_iconv(&diriter->ic, &filename, &filename_len)) < 0)
+ return error;
+#endif
+
+ git_buf_truncate(&diriter->path, diriter->parent_len);
+
+ if (diriter->parent_len > 0 &&
+ diriter->path.ptr[diriter->parent_len-1] != '/')
+ git_buf_putc(&diriter->path, '/');
+
+ git_buf_put(&diriter->path, filename, filename_len);
+
+ if (git_buf_oom(&diriter->path))
+ return -1;
+
+ return error;
+}
+
+int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ assert(diriter->path.size > diriter->parent_len);
+
+ *out = &diriter->path.ptr[diriter->parent_len+1];
+ *out_len = diriter->path.size - diriter->parent_len - 1;
+ return 0;
+}
+
+int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ *out = diriter->path.ptr;
+ *out_len = diriter->path.size;
+ return 0;
+}
+
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
+{
+ assert(out && diriter);
+
+ return git_path_lstat(diriter->path.ptr, out);
+}
+
+void git_path_diriter_free(git_path_diriter *diriter)
+{
+ if (diriter == NULL)
+ return;
+
+ if (diriter->dir) {
+ closedir(diriter->dir);
+ diriter->dir = NULL;
+ }
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&diriter->ic);
+#endif
+
+ git_buf_free(&diriter->path);
+}
+
+#endif
+
+int git_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags)
+{
+ git_path_diriter iter = GIT_PATH_DIRITER_INIT;
+ const char *name;
+ size_t name_len;
+ char *dup;
+ int error;
+
+ assert(contents && path);
+
+ if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
+ return error;
+
+ while ((error = git_path_diriter_next(&iter)) == 0) {
+ if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
+ break;
+
+ assert(name_len > prefix_len);
+
+ dup = git__strndup(name + prefix_len, name_len - prefix_len);
+ GITERR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(contents, dup)) < 0)
+ break;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_path_diriter_free(&iter);
+ return error;
+}
+
+int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
+{
+ if (git_path_is_local_file_url(url_or_path))
+ return git_path_fromurl(local_path_out, url_or_path);
+ else
+ return git_buf_sets(local_path_out, url_or_path);
+}
+
+/* Reject paths like AUX or COM1, or those versions that end in a dot or
+ * colon. ("AUX." or "AUX:")
+ */
+GIT_INLINE(bool) verify_dospath(
+ const char *component,
+ size_t len,
+ const char dospath[3],
+ bool trailing_num)
+{
+ size_t last = trailing_num ? 4 : 3;
+
+ if (len < last || git__strncasecmp(component, dospath, 3) != 0)
+ return true;
+
+ if (trailing_num && (component[3] < '1' || component[3] > '9'))
+ return true;
+
+ return (len > last &&
+ component[last] != '.' &&
+ component[last] != ':');
+}
+
+static int32_t next_hfs_char(const char **in, size_t *len)
+{
+ while (*len) {
+ int32_t codepoint;
+ int cp_len = git__utf8_iterate((const uint8_t *)(*in), (int)(*len), &codepoint);
+ if (cp_len < 0)
+ return -1;
+
+ (*in) += cp_len;
+ (*len) -= cp_len;
+
+ /* these code points are ignored completely */
+ switch (codepoint) {
+ case 0x200c: /* ZERO WIDTH NON-JOINER */
+ case 0x200d: /* ZERO WIDTH JOINER */
+ case 0x200e: /* LEFT-TO-RIGHT MARK */
+ case 0x200f: /* RIGHT-TO-LEFT MARK */
+ case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
+ case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
+ case 0x202c: /* POP DIRECTIONAL FORMATTING */
+ case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
+ case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
+ case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
+ case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
+ case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
+ case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
+ case 0x206e: /* NATIONAL DIGIT SHAPES */
+ case 0x206f: /* NOMINAL DIGIT SHAPES */
+ case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
+ continue;
+ }
+
+ /* fold into lowercase -- this will only fold characters in
+ * the ASCII range, which is perfectly fine, because the
+ * git folder name can only be composed of ascii characters
+ */
+ return git__tolower(codepoint);
+ }
+ return 0; /* NULL byte -- end of string */
+}
+
+static bool verify_dotgit_hfs(const char *path, size_t len)
+{
+ if (next_hfs_char(&path, &len) != '.' ||
+ next_hfs_char(&path, &len) != 'g' ||
+ next_hfs_char(&path, &len) != 'i' ||
+ next_hfs_char(&path, &len) != 't' ||
+ next_hfs_char(&path, &len) != 0)
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
+{
+ git_buf *reserved = git_repository__reserved_names_win32;
+ size_t reserved_len = git_repository__reserved_names_win32_len;
+ size_t start = 0, i;
+
+ if (repo)
+ git_repository__reserved_names(&reserved, &reserved_len, repo, true);
+
+ for (i = 0; i < reserved_len; i++) {
+ git_buf *r = &reserved[i];
+
+ if (len >= r->size &&
+ strncasecmp(path, r->ptr, r->size) == 0) {
+ start = r->size;
+ break;
+ }
+ }
+
+ if (!start)
+ return true;
+
+ /* Reject paths like ".git\" */
+ if (path[start] == '\\')
+ return false;
+
+ /* Reject paths like '.git ' or '.git.' */
+ for (i = start; i < len; i++) {
+ if (path[i] != ' ' && path[i] != '.')
+ return true;
+ }
+
+ return false;
+}
+
+GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
+{
+ if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_SLASH) && c == '/')
+ return false;
+
+ if (flags & GIT_PATH_REJECT_NT_CHARS) {
+ if (c < 32)
+ return false;
+
+ switch (c) {
+ case '<':
+ case '>':
+ case ':':
+ case '"':
+ case '|':
+ case '?':
+ case '*':
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * We fundamentally don't like some paths when dealing with user-inputted
+ * strings (in checkout or ref names): we don't want dot or dot-dot
+ * anywhere, we want to avoid writing weird paths on Windows that can't
+ * be handled by tools that use the non-\\?\ APIs, we don't want slashes
+ * or double slashes at the end of paths that can make them ambiguous.
+ *
+ * For checkout, we don't want to recurse into ".git" either.
+ */
+static bool verify_component(
+ git_repository *repo,
+ const char *component,
+ size_t len,
+ unsigned int flags)
+{
+ if (len == 0)
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
+ len == 1 && component[0] == '.')
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
+ len == 2 && component[0] == '.' && component[1] == '.')
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.')
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_TRAILING_SPACE) && component[len-1] == ' ')
+ return false;
+
+ if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':')
+ return false;
+
+ if (flags & GIT_PATH_REJECT_DOS_PATHS) {
+ if (!verify_dospath(component, len, "CON", false) ||
+ !verify_dospath(component, len, "PRN", false) ||
+ !verify_dospath(component, len, "AUX", false) ||
+ !verify_dospath(component, len, "NUL", false) ||
+ !verify_dospath(component, len, "COM", true) ||
+ !verify_dospath(component, len, "LPT", true))
+ return false;
+ }
+
+ if (flags & GIT_PATH_REJECT_DOT_GIT_HFS &&
+ !verify_dotgit_hfs(component, len))
+ return false;
+
+ if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS &&
+ !verify_dotgit_ntfs(repo, component, len))
+ return false;
+
+ /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
+ * specific tests, they would have already rejected `.git`.
+ */
+ if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 &&
+ (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
+ (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL) &&
+ len == 4 &&
+ component[0] == '.' &&
+ (component[1] == 'g' || component[1] == 'G') &&
+ (component[2] == 'i' || component[2] == 'I') &&
+ (component[3] == 't' || component[3] == 'T'))
+ return false;
+
+ return true;
+}
+
+GIT_INLINE(unsigned int) dotgit_flags(
+ git_repository *repo,
+ unsigned int flags)
+{
+ int protectHFS = 0, protectNTFS = 0;
+
+ flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
+
+#ifdef __APPLE__
+ protectHFS = 1;
+#endif
+
+#ifdef GIT_WIN32
+ protectNTFS = 1;
+#endif
+
+ if (repo && !protectHFS)
+ git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS);
+ if (protectHFS)
+ flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
+
+ if (repo && !protectNTFS)
+ git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS);
+ if (protectNTFS)
+ flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
+
+ return flags;
+}
+
+bool git_path_isvalid(
+ git_repository *repo,
+ const char *path,
+ unsigned int flags)
+{
+ const char *start, *c;
+
+ /* Upgrade the ".git" checks based on platform */
+ if ((flags & GIT_PATH_REJECT_DOT_GIT))
+ flags = dotgit_flags(repo, flags);
+
+ for (start = c = path; *c; c++) {
+ if (!verify_char(*c, flags))
+ return false;
+
+ if (*c == '/') {
+ if (!verify_component(repo, start, (c - start), flags))
+ return false;
+
+ start = c+1;
+ }
+ }
+
+ return verify_component(repo, start, (c - start), flags);
+}
+
+int git_path_normalize_slashes(git_buf *out, const char *path)
+{
+ int error;
+ char *p;
+
+ if ((error = git_buf_puts(out, path)) < 0)
+ return error;
+
+ for (p = out->ptr; *p; p++) {
+ if (*p == '\\')
+ *p = '/';
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_path_h__
+#define INCLUDE_path_h__
+
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
+#include "vector.h"
+
+/**
+ * Path manipulation utils
+ *
+ * These are path utilities that munge paths without actually
+ * looking at the real filesystem.
+ */
+
+/*
+ * The dirname() function shall take a pointer to a character string
+ * that contains a pathname, and return a pointer to a string that is a
+ * pathname of the parent directory of that file. Trailing '/' characters
+ * in the path are not counted as part of the path.
+ *
+ * If path does not contain a '/', then dirname() shall return a pointer to
+ * the string ".". If path is a null pointer or points to an empty string,
+ * dirname() shall return a pointer to the string "." .
+ *
+ * The `git_path_dirname` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git_path_dirname_r` implementation writes the dirname to a `git_buf`
+ * if the buffer pointer is not NULL.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the dirname (which will be > 0).
+ */
+extern char *git_path_dirname(const char *path);
+extern int git_path_dirname_r(git_buf *buffer, const char *path);
+
+/*
+ * This function returns the basename of the file, which is the last
+ * part of its full name given by fname, with the drive letter and
+ * leading directories stripped off. For example, the basename of
+ * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo.
+ *
+ * Trailing slashes and backslashes are significant: the basename of
+ * c:/foo/bar/ is an empty string after the rightmost slash.
+ *
+ * The `git_path_basename` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git_path_basename_r` implementation writes the basename to a `git_buf`.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the basename (which will be >= 0).
+ */
+extern char *git_path_basename(const char *path);
+extern int git_path_basename_r(git_buf *buffer, const char *path);
+
+/* Return the offset of the start of the basename. Unlike the other
+ * basename functions, this returns 0 if the path is empty.
+ */
+extern size_t git_path_basename_offset(git_buf *buffer);
+
+extern const char *git_path_topdir(const char *path);
+
+/**
+ * Find offset to root of path if path has one.
+ *
+ * This will return a number >= 0 which is the offset to the start of the
+ * path, if the path is rooted (i.e. "/rooted/path" returns 0 and
+ * "c:/windows/rooted/path" returns 2). If the path is not rooted, this
+ * returns -1.
+ */
+extern int git_path_root(const char *path);
+
+/**
+ * Ensure path has a trailing '/'.
+ */
+extern int git_path_to_dir(git_buf *path);
+
+/**
+ * Ensure string has a trailing '/' if there is space for it.
+ */
+extern void git_path_string_to_dir(char* path, size_t size);
+
+/**
+ * Taken from git.git; returns nonzero if the given path is "." or "..".
+ */
+GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name)
+{
+ return (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')));
+}
+
+#ifdef GIT_WIN32
+GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name)
+{
+ return (name[0] == L'.' &&
+ (name[1] == L'\0' ||
+ (name[1] == L'.' && name[2] == L'\0')));
+}
+
+/**
+ * Convert backslashes in path to forward slashes.
+ */
+GIT_INLINE(void) git_path_mkposix(char *path)
+{
+ while (*path) {
+ if (*path == '\\')
+ *path = '/';
+
+ path++;
+ }
+}
+#else
+# define git_path_mkposix(p) /* blank */
+#endif
+
+/**
+ * Check if string is a relative path (i.e. starts with "./" or "../")
+ */
+GIT_INLINE(int) git_path_is_relative(const char *p)
+{
+ return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')));
+}
+
+/**
+ * Check if string is at end of path segment (i.e. looking at '/' or '\0')
+ */
+GIT_INLINE(int) git_path_at_end_of_segment(const char *p)
+{
+ return !*p || *p == '/';
+}
+
+extern int git__percent_decode(git_buf *decoded_out, const char *input);
+
+/**
+ * Extract path from file:// URL.
+ */
+extern int git_path_fromurl(git_buf *local_path_out, const char *file_url);
+
+
+/**
+ * Path filesystem utils
+ *
+ * These are path utilities that actually access the filesystem.
+ */
+
+/**
+ * Check if a file exists and can be accessed.
+ * @return true or false
+ */
+extern bool git_path_exists(const char *path);
+
+/**
+ * Check if the given path points to a directory.
+ * @return true or false
+ */
+extern bool git_path_isdir(const char *path);
+
+/**
+ * Check if the given path points to a regular file.
+ * @return true or false
+ */
+extern bool git_path_isfile(const char *path);
+
+/**
+ * Check if the given path points to a symbolic link.
+ * @return true or false
+ */
+extern bool git_path_islink(const char *path);
+
+/**
+ * Check if the given path is a directory, and is empty.
+ */
+extern bool git_path_is_empty_dir(const char *path);
+
+/**
+ * Stat a file and/or link and set error if needed.
+ */
+extern int git_path_lstat(const char *path, struct stat *st);
+
+/**
+ * Check if the parent directory contains the item.
+ *
+ * @param dir Directory to check.
+ * @param item Item that might be in the directory.
+ * @return 0 if item exists in directory, <0 otherwise.
+ */
+extern bool git_path_contains(git_buf *dir, const char *item);
+
+/**
+ * Check if the given path contains the given subdirectory.
+ *
+ * @param parent Directory path that might contain subdir
+ * @param subdir Subdirectory name to look for in parent
+ * @return true if subdirectory exists, false otherwise.
+ */
+extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
+
+/**
+ * Determine the common directory length between two paths, including
+ * the final path separator. For example, given paths 'a/b/c/1.txt
+ * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this
+ * will return the length of the string 'a/b/c/', which is 6.
+ *
+ * @param one The first path
+ * @param two The second path
+ * @return The length of the common directory
+ */
+extern size_t git_path_common_dirlen(const char *one, const char *two);
+
+/**
+ * Make the path relative to the given parent path.
+ *
+ * @param path The path to make relative
+ * @param parent The parent path to make path relative to
+ * @return 0 if path was made relative, GIT_ENOTFOUND
+ * if there was not common root between the paths,
+ * or <0.
+ */
+extern int git_path_make_relative(git_buf *path, const char *parent);
+
+/**
+ * Check if the given path contains the given file.
+ *
+ * @param dir Directory path that might contain file
+ * @param file File name to look for in parent
+ * @return true if file exists, false otherwise.
+ */
+extern bool git_path_contains_file(git_buf *dir, const char *file);
+
+/**
+ * Prepend base to unrooted path or just copy path over.
+ *
+ * This will optionally return the index into the path where the "root"
+ * is, either the end of the base directory prefix or the path root.
+ */
+extern int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
+
+/**
+ * Removes multiple occurrences of '/' in a row, squashing them into a
+ * single '/'.
+ */
+extern void git_path_squash_slashes(git_buf *path);
+
+/**
+ * Clean up path, prepending base if it is not already rooted.
+ */
+extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
+
+/**
+ * Clean up path, prepending base if it is not already rooted and
+ * appending a slash.
+ */
+extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base);
+
+/**
+ * Get a directory from a path.
+ *
+ * If path is a directory, this acts like `git_path_prettify_dir`
+ * (cleaning up path and appending a '/'). If path is a normal file,
+ * this prettifies it, then removed the filename a la dirname and
+ * appends the trailing '/'. If the path does not exist, it is
+ * treated like a regular filename.
+ */
+extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
+
+/**
+ * Resolve relative references within a path.
+ *
+ * This eliminates "./" and "../" relative references inside a path,
+ * as well as condensing multiple slashes into single ones. It will
+ * not touch the path before the "ceiling" length.
+ *
+ * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL
+ * prefix and not touch that part of the path.
+ */
+extern int git_path_resolve_relative(git_buf *path, size_t ceiling);
+
+/**
+ * Apply a relative path to base path.
+ *
+ * Note that the base path could be a filename or a URL and this
+ * should still work. The relative path is walked segment by segment
+ * with three rules: series of slashes will be condensed to a single
+ * slash, "." will be eaten with no change, and ".." will remove a
+ * segment from the base path.
+ */
+extern int git_path_apply_relative(git_buf *target, const char *relpath);
+
+enum {
+ GIT_PATH_DIR_IGNORE_CASE = (1u << 0),
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1),
+ GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2),
+};
+
+/**
+ * Walk each directory entry, except '.' and '..', calling fn(state).
+ *
+ * @param pathbuf Buffer the function reads the initial directory
+ * path from, and updates with each successive entry's name.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ * @param callback Callback for each entry. Passed the `payload` and each
+ * successive path inside the directory as a full path. This may
+ * safely append text to the pathbuf if needed. Return non-zero to
+ * cancel iteration (and return value will be propagated back).
+ * @param payload Passed to callback as first argument.
+ * @return 0 on success or error code from OS error or from callback
+ */
+extern int git_path_direach(
+ git_buf *pathbuf,
+ uint32_t flags,
+ int (*callback)(void *payload, git_buf *path),
+ void *payload);
+
+/**
+ * Sort function to order two paths
+ */
+extern int git_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t));
+
+/**
+ * Invoke callback up path directory by directory until the ceiling is
+ * reached (inclusive of a final call at the root_path).
+ *
+ * Returning anything other than 0 from the callback function
+ * will stop the iteration and propagate the error to the caller.
+ *
+ * @param pathbuf Buffer the function reads the directory from and
+ * and updates with each successive name.
+ * @param ceiling Prefix of path at which to stop walking up. If NULL,
+ * this will walk all the way up to the root. If not a prefix of
+ * pathbuf, the callback will be invoked a single time on the
+ * original input path.
+ * @param callback Function to invoke on each path. Passed the `payload`
+ * and the buffer containing the current path. The path should not
+ * be modified in any way. Return non-zero to stop iteration.
+ * @param payload Passed to fn as the first ath.
+ */
+extern int git_path_walk_up(
+ git_buf *pathbuf,
+ const char *ceiling,
+ int (*callback)(void *payload, const char *path),
+ void *payload);
+
+
+enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 };
+
+/*
+ * Determines if a path is equal to or potentially a child of another.
+ * @param parent The possible parent
+ * @param child The possible child
+ */
+GIT_INLINE(int) git_path_equal_or_prefixed(
+ const char *parent,
+ const char *child,
+ ssize_t *prefixlen)
+{
+ const char *p = parent, *c = child;
+ int lastslash = 0;
+
+ while (*p && *c) {
+ lastslash = (*p == '/');
+
+ if (*p++ != *c++)
+ return GIT_PATH_NOTEQUAL;
+ }
+
+ if (*p != '\0')
+ return GIT_PATH_NOTEQUAL;
+
+ if (*c == '\0') {
+ if (prefixlen)
+ *prefixlen = p - parent;
+
+ return GIT_PATH_EQUAL;
+ }
+
+ if (*c == '/' || lastslash) {
+ if (prefixlen)
+ *prefixlen = (p - parent) - lastslash;
+
+ return GIT_PATH_PREFIX;
+ }
+
+ return GIT_PATH_NOTEQUAL;
+}
+
+/* translate errno to libgit2 error code and set error message */
+extern int git_path_set_error(
+ int errno_value, const char *path, const char *action);
+
+/* check if non-ascii characters are present in filename */
+extern bool git_path_has_non_ascii(const char *path, size_t pathlen);
+
+#define GIT_PATH_REPO_ENCODING "UTF-8"
+
+#ifdef __APPLE__
+#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC"
+#else
+#define GIT_PATH_NATIVE_ENCODING "UTF-8"
+#endif
+
+#ifdef GIT_USE_ICONV
+
+#include <iconv.h>
+
+typedef struct {
+ iconv_t map;
+ git_buf buf;
+} git_path_iconv_t;
+
+#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_BUF_INIT }
+
+/* Init iconv data for converting decomposed UTF-8 to precomposed */
+extern int git_path_iconv_init_precompose(git_path_iconv_t *ic);
+
+/* Clear allocated iconv data */
+extern void git_path_iconv_clear(git_path_iconv_t *ic);
+
+/*
+ * Rewrite `in` buffer using iconv map if necessary, replacing `in`
+ * pointer internal iconv buffer if rewrite happened. The `in` pointer
+ * will be left unchanged if no rewrite was needed.
+ */
+extern int git_path_iconv(git_path_iconv_t *ic, const char **in, size_t *inlen);
+
+#endif /* GIT_USE_ICONV */
+
+extern bool git_path_does_fs_decompose_unicode(const char *root);
+
+
+typedef struct git_path_diriter git_path_diriter;
+
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+struct git_path_diriter
+{
+ git_win32_path path;
+ size_t parent_len;
+
+ git_buf path_utf8;
+ size_t parent_utf8_len;
+
+ HANDLE handle;
+
+ unsigned int flags;
+
+ WIN32_FIND_DATAW current;
+ unsigned int needs_next;
+};
+
+#define GIT_PATH_DIRITER_INIT { {0}, 0, GIT_BUF_INIT, 0, INVALID_HANDLE_VALUE }
+
+#else
+
+struct git_path_diriter
+{
+ git_buf path;
+ size_t parent_len;
+
+ unsigned int flags;
+
+ DIR *dir;
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic;
+#endif
+};
+
+#define GIT_PATH_DIRITER_INIT { GIT_BUF_INIT }
+
+#endif
+
+/**
+ * Initialize a directory iterator.
+ *
+ * @param diriter Pointer to a diriter structure that will be setup.
+ * @param path The path that will be iterated over
+ * @param flags Directory reader flags
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_init(
+ git_path_diriter *diriter,
+ const char *path,
+ unsigned int flags);
+
+/**
+ * Advance the directory iterator. Will return GIT_ITEROVER when
+ * the iteration has completed successfully.
+ *
+ * @param diriter The directory iterator
+ * @return 0, GIT_ITEROVER, or an error code
+ */
+extern int git_path_diriter_next(git_path_diriter *diriter);
+
+/**
+ * Returns the file name of the current item in the iterator.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter);
+
+/**
+ * Returns the full path of the current item in the iterator; that
+ * is the current filename plus the path of the directory that the
+ * iterator was constructed with.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter);
+
+/**
+ * Performs an `lstat` on the current item in the iterator.
+ *
+ * @param out Pointer to store the stat data in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter);
+
+/**
+ * Closes the directory iterator.
+ *
+ * @param diriter The directory iterator
+ */
+extern void git_path_diriter_free(git_path_diriter *diriter);
+
+/**
+ * Load all directory entries (except '.' and '..') into a vector.
+ *
+ * For cases where `git_path_direach()` is not appropriate, this
+ * allows you to load the filenames in a directory into a vector
+ * of strings. That vector can then be sorted, iterated, or whatever.
+ * Remember to free alloc of the allocated strings when you are done.
+ *
+ * @param contents Vector to fill with directory entry names.
+ * @param path The directory to read from.
+ * @param prefix_len When inserting entries, the trailing part of path
+ * will be prefixed after this length. I.e. given path "/a/b" and
+ * prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ */
+extern int git_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags);
+
+
+/* Used for paths to repositories on the filesystem */
+extern bool git_path_is_local_file_url(const char *file_url);
+extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
+
+/* Flags to determine path validity in `git_path_isvalid` */
+#define GIT_PATH_REJECT_TRAVERSAL (1 << 0)
+#define GIT_PATH_REJECT_DOT_GIT (1 << 1)
+#define GIT_PATH_REJECT_SLASH (1 << 2)
+#define GIT_PATH_REJECT_BACKSLASH (1 << 3)
+#define GIT_PATH_REJECT_TRAILING_DOT (1 << 4)
+#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 5)
+#define GIT_PATH_REJECT_TRAILING_COLON (1 << 6)
+#define GIT_PATH_REJECT_DOS_PATHS (1 << 7)
+#define GIT_PATH_REJECT_NT_CHARS (1 << 8)
+#define GIT_PATH_REJECT_DOT_GIT_LITERAL (1 << 9)
+#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 10)
+#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 11)
+
+/* Default path safety for writing files to disk: since we use the
+ * Win32 "File Namespace" APIs ("\\?\") we need to protect from
+ * paths that the normal Win32 APIs would not write.
+ */
+#ifdef GIT_WIN32
+# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \
+ GIT_PATH_REJECT_TRAVERSAL | \
+ GIT_PATH_REJECT_BACKSLASH | \
+ GIT_PATH_REJECT_TRAILING_DOT | \
+ GIT_PATH_REJECT_TRAILING_SPACE | \
+ GIT_PATH_REJECT_TRAILING_COLON | \
+ GIT_PATH_REJECT_DOS_PATHS | \
+ GIT_PATH_REJECT_NT_CHARS
+#else
+# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \
+ GIT_PATH_REJECT_TRAVERSAL
+#endif
+
+ /* Paths that should never be written into the working directory. */
+#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \
+ GIT_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT
+
+/* Paths that should never be written to the index. */
+#define GIT_PATH_REJECT_INDEX_DEFAULTS \
+ GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT
+
+/*
+ * Determine whether a path is a valid git path or not - this must not contain
+ * a '.' or '..' component, or a component that is ".git" (in any case).
+ *
+ * `repo` is optional. If specified, it will be used to determine the short
+ * path name to reject (if `GIT_PATH_REJECT_DOS_SHORTNAME` is specified),
+ * in addition to the default of "git~1".
+ */
+extern bool git_path_isvalid(
+ git_repository *repo,
+ const char *path,
+ unsigned int flags);
+
+/**
+ * Convert any backslashes into slashes
+ */
+int git_path_normalize_slashes(git_buf *out, const char *path);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/pathspec.h"
+#include "git2/diff.h"
+#include "pathspec.h"
+#include "buf_text.h"
+#include "attr_file.h"
+#include "iterator.h"
+#include "repository.h"
+#include "index.h"
+#include "bitvec.h"
+#include "diff.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+char *git_pathspec_prefix(const git_strarray *pathspec)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ const char *scan;
+
+ if (!pathspec || !pathspec->count ||
+ git_buf_text_common_prefix(&prefix, pathspec) < 0)
+ return NULL;
+
+ /* diff prefix will only be leading non-wildcards */
+ for (scan = prefix.ptr; *scan; ++scan) {
+ if (git__iswildcard(*scan) &&
+ (scan == prefix.ptr || (*(scan - 1) != '\\')))
+ break;
+ }
+ git_buf_truncate(&prefix, scan - prefix.ptr);
+
+ if (prefix.size <= 0) {
+ git_buf_free(&prefix);
+ return NULL;
+ }
+
+ git_buf_text_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_pathspec_is_empty(const git_strarray *pathspec)
+{
+ size_t i;
+
+ if (pathspec == NULL)
+ return true;
+
+ for (i = 0; i < pathspec->count; ++i) {
+ const char *str = pathspec->strings[i];
+
+ if (str && str[0])
+ return false;
+ }
+
+ return true;
+}
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+int git_pathspec__vinit(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
+{
+ size_t i;
+
+ memset(vspec, 0, sizeof(*vspec));
+
+ if (git_pathspec_is_empty(strspec))
+ return 0;
+
+ if (git_vector_init(vspec, strspec->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < strspec->count; ++i) {
+ int ret;
+ const char *pattern = strspec->strings[i];
+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ return -1;
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE |
+ GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_NOLEADINGDIR;
+
+ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0) {
+ git__free(match);
+ return ret;
+ }
+
+ if (git_vector_insert(vspec, match) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* free data from the pathspec vector */
+void git_pathspec__vfree(git_vector *vspec)
+{
+ git_vector_free_deep(vspec);
+}
+
+struct pathspec_match_context {
+ int fnmatch_flags;
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+};
+
+static void pathspec_match_context_init(
+ struct pathspec_match_context *ctxt,
+ bool disable_fnmatch,
+ bool casefold)
+{
+ if (disable_fnmatch)
+ ctxt->fnmatch_flags = -1;
+ else if (casefold)
+ ctxt->fnmatch_flags = FNM_CASEFOLD;
+ else
+ ctxt->fnmatch_flags = 0;
+
+ if (casefold) {
+ ctxt->strcomp = git__strcasecmp;
+ ctxt->strncomp = git__strncasecmp;
+ } else {
+ ctxt->strcomp = git__strcmp;
+ ctxt->strncomp = git__strncmp;
+ }
+}
+
+static int pathspec_match_one(
+ const git_attr_fnmatch *match,
+ struct pathspec_match_context *ctxt,
+ const char *path)
+{
+ int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
+
+ if (result == FNM_NOMATCH)
+ result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ ctxt->strncomp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ /* if we didn't match and this is a negative match, check for exact
+ * match of filename with leading '!'
+ */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 &&
+ *path == '!' &&
+ ctxt->strncomp(path + 1, match->pattern, match->length) == 0 &&
+ (!path[match->length + 1] || path[match->length + 1] == '/'))
+ return 1;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1;
+ return -1;
+}
+
+static int git_pathspec__match_at(
+ size_t *matched_at,
+ const git_vector *vspec,
+ struct pathspec_match_context *ctxt,
+ const char *path0,
+ const char *path1)
+{
+ int result = GIT_ENOTFOUND;
+ size_t i = 0;
+ const git_attr_fnmatch *match;
+
+ git_vector_foreach(vspec, i, match) {
+ if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0)
+ break;
+ if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0)
+ break;
+ }
+
+ *matched_at = i;
+ return result;
+}
+
+/* match a path against the vectorized pathspec */
+bool git_pathspec__match(
+ const git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec,
+ size_t *matched_at)
+{
+ int result;
+ size_t pos;
+ struct pathspec_match_context ctxt;
+
+ if (matched_pathspec)
+ *matched_pathspec = NULL;
+ if (matched_at)
+ *matched_at = GIT_PATHSPEC_NOMATCH;
+
+ if (!vspec || !vspec->length)
+ return true;
+
+ pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
+
+ result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL);
+ if (result >= 0) {
+ if (matched_pathspec) {
+ const git_attr_fnmatch *match = git_vector_get(vspec, pos);
+ *matched_pathspec = match->pattern;
+ }
+
+ if (matched_at)
+ *matched_at = pos;
+ }
+
+ return (result > 0);
+}
+
+
+int git_pathspec__init(git_pathspec *ps, const git_strarray *paths)
+{
+ int error = 0;
+
+ memset(ps, 0, sizeof(*ps));
+
+ ps->prefix = git_pathspec_prefix(paths);
+ git_pool_init(&ps->pool, 1);
+
+ if ((error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0)
+ git_pathspec__clear(ps);
+
+ return error;
+}
+
+void git_pathspec__clear(git_pathspec *ps)
+{
+ git__free(ps->prefix);
+ git_pathspec__vfree(&ps->pathspec);
+ git_pool_clear(&ps->pool);
+ memset(ps, 0, sizeof(*ps));
+}
+
+int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec)
+{
+ int error = 0;
+ git_pathspec *ps = git__malloc(sizeof(git_pathspec));
+ GITERR_CHECK_ALLOC(ps);
+
+ if ((error = git_pathspec__init(ps, pathspec)) < 0) {
+ git__free(ps);
+ return error;
+ }
+
+ GIT_REFCOUNT_INC(ps);
+ *out = ps;
+ return 0;
+}
+
+static void pathspec_free(git_pathspec *ps)
+{
+ git_pathspec__clear(ps);
+ git__free(ps);
+}
+
+void git_pathspec_free(git_pathspec *ps)
+{
+ if (!ps)
+ return;
+ GIT_REFCOUNT_DEC(ps, pathspec_free);
+}
+
+int git_pathspec_matches_path(
+ const git_pathspec *ps, uint32_t flags, const char *path)
+{
+ bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0;
+ bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0;
+
+ assert(ps && path);
+
+ return (0 != git_pathspec__match(
+ &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL));
+}
+
+static void pathspec_match_free(git_pathspec_match_list *m)
+{
+ if (!m)
+ return;
+
+ git_pathspec_free(m->pathspec);
+ m->pathspec = NULL;
+
+ git_array_clear(m->matches);
+ git_array_clear(m->failures);
+ git_pool_clear(&m->pool);
+ git__free(m);
+}
+
+static git_pathspec_match_list *pathspec_match_alloc(
+ git_pathspec *ps, int datatype)
+{
+ git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list));
+ if (!m)
+ return NULL;
+
+ git_pool_init(&m->pool, 1);
+
+ /* need to keep reference to pathspec and increment refcount because
+ * failures array stores pointers to the pattern strings of the
+ * pathspec that had no matches
+ */
+ GIT_REFCOUNT_INC(ps);
+ m->pathspec = ps;
+ m->datatype = datatype;
+
+ return m;
+}
+
+GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos)
+{
+ if (!git_bitvec_get(used, pos)) {
+ git_bitvec_set(used, pos, true);
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t pathspec_mark_remaining(
+ git_bitvec *used,
+ git_vector *patterns,
+ struct pathspec_match_context *ctxt,
+ size_t start,
+ const char *path0,
+ const char *path1)
+{
+ size_t count = 0;
+
+ if (path1 == path0)
+ path1 = NULL;
+
+ for (; start < patterns->length; ++start) {
+ const git_attr_fnmatch *pat = git_vector_get(patterns, start);
+
+ if (git_bitvec_get(used, start))
+ continue;
+
+ if (path0 && pathspec_match_one(pat, ctxt, path0) > 0)
+ count += pathspec_mark_pattern(used, start);
+ else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0)
+ count += pathspec_mark_pattern(used, start);
+ }
+
+ return count;
+}
+
+static int pathspec_build_failure_array(
+ git_pathspec_string_array_t *failures,
+ git_vector *patterns,
+ git_bitvec *used,
+ git_pool *pool)
+{
+ size_t pos;
+ char **failed;
+ const git_attr_fnmatch *pat;
+
+ for (pos = 0; pos < patterns->length; ++pos) {
+ if (git_bitvec_get(used, pos))
+ continue;
+
+ if ((failed = git_array_alloc(*failures)) == NULL)
+ return -1;
+
+ pat = git_vector_get(patterns, pos);
+
+ if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pathspec_match_from_iterator(
+ git_pathspec_match_list **out,
+ git_iterator *iter,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ const git_index_entry *entry = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t pos, used_ct = 0, found_files = 0;
+ git_index *index = NULL;
+ git_bitvec used_patterns;
+ char **file;
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0)
+ goto done;
+
+ if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
+ (error = git_repository_index__weakptr(
+ &index, git_iterator_owner(iter))) < 0)
+ goto done;
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_iterator_ignore_case(iter));
+
+ while (!(error = git_iterator_advance(&entry, iter))) {
+ /* search for match with entry->path */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, entry->path, NULL);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result) {
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ continue;
+ }
+
+ /* check if path is ignored and untracked */
+ if (index != NULL &&
+ git_iterator_current_is_ignored(iter) &&
+ git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ ++found_files;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
+ }
+
+ /* insert matched path into matches array */
+ if ((file = (char **)git_array_alloc(m->matches)) == NULL ||
+ (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ error = 0;
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) {
+ giterr_set(GITERR_INVALID, "No matching files were found");
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags)
+{
+ git_iterator_flag_t f = 0;
+
+ if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0)
+ f |= GIT_ITERATOR_IGNORE_CASE;
+ else if ((flags & GIT_PATHSPEC_USE_CASE) != 0)
+ f |= GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ return f;
+}
+
+int git_pathspec_match_workdir(
+ git_pathspec_match_list **out,
+ git_repository *repo,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ git_iterator *iter;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error = 0;
+
+ assert(repo);
+
+ iter_opts.flags = pathspec_match_iter_flags(flags);
+
+ if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) {
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_index(
+ git_pathspec_match_list **out,
+ git_index *index,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ git_iterator *iter;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error = 0;
+
+ assert(index);
+
+ iter_opts.flags = pathspec_match_iter_flags(flags);
+
+ if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) {
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_tree(
+ git_pathspec_match_list **out,
+ git_tree *tree,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ git_iterator *iter;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error = 0;
+
+ assert(tree);
+
+ iter_opts.flags = pathspec_match_iter_flags(flags);
+
+ if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) {
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_diff(
+ git_pathspec_match_list **out,
+ git_diff *diff,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t i, pos, used_ct = 0, found_deltas = 0;
+ const git_diff_delta *delta, **match;
+ git_bitvec used_patterns;
+
+ assert(diff);
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_diff_is_sorted_icase(diff));
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ /* search for match with delta */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result)
+ continue;
+
+ ++found_deltas;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1,
+ delta->old_file.path, delta->new_file.path);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
+ }
+
+ /* insert matched delta into matches array */
+ if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) {
+ error = -1;
+ goto done;
+ } else {
+ *match = delta;
+ }
+ }
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) {
+ giterr_set(GITERR_INVALID, "No matching deltas were found");
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+void git_pathspec_match_list_free(git_pathspec_match_list *m)
+{
+ if (m)
+ pathspec_match_free(m);
+}
+
+size_t git_pathspec_match_list_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->matches) : 0;
+}
+
+const char *git_pathspec_match_list_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const char **)git_array_get(m->matches, pos));
+}
+
+const git_diff_delta *git_pathspec_match_list_diff_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const git_diff_delta **)git_array_get(m->matches, pos));
+}
+
+size_t git_pathspec_match_list_failed_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->failures) : 0;
+}
+
+const char * git_pathspec_match_list_failed_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ char **entry = m ? git_array_get(m->failures, pos) : NULL;
+
+ return entry ? *entry : NULL;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_pathspec_h__
+#define INCLUDE_pathspec_h__
+
+#include "common.h"
+#include <git2/pathspec.h>
+#include "buffer.h"
+#include "vector.h"
+#include "pool.h"
+#include "array.h"
+
+/* public compiled pathspec */
+struct git_pathspec {
+ git_refcount rc;
+ char *prefix;
+ git_vector pathspec;
+ git_pool pool;
+};
+
+enum {
+ PATHSPEC_DATATYPE_STRINGS = 0,
+ PATHSPEC_DATATYPE_DIFF = 1,
+};
+
+typedef git_array_t(char *) git_pathspec_string_array_t;
+
+/* public interface to pathspec matching */
+struct git_pathspec_match_list {
+ git_pathspec *pathspec;
+ git_array_t(void *) matches;
+ git_pathspec_string_array_t failures;
+ git_pool pool;
+ int datatype;
+};
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+extern char *git_pathspec_prefix(const git_strarray *pathspec);
+
+/* is there anything in the spec that needs to be filtered on */
+extern bool git_pathspec_is_empty(const git_strarray *pathspec);
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+extern int git_pathspec__vinit(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool);
+
+/* free data from the pathspec vector */
+extern void git_pathspec__vfree(git_vector *vspec);
+
+#define GIT_PATHSPEC_NOMATCH ((size_t)-1)
+
+/*
+ * Match a path against the vectorized pathspec.
+ * The matched pathspec is passed back into the `matched_pathspec` parameter,
+ * unless it is passed as NULL by the caller.
+ */
+extern bool git_pathspec__match(
+ const git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec,
+ size_t *matched_at);
+
+/* easy pathspec setup */
+
+extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths);
+
+extern void git_pathspec__clear(git_pathspec *ps);
+
+#endif
--- /dev/null
+#include "pool.h"
+#include "posix.h"
+#ifndef GIT_WIN32
+#include <unistd.h>
+#endif
+
+struct git_pool_page {
+ git_pool_page *next;
+ uint32_t size;
+ uint32_t avail;
+ GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8);
+};
+
+static void *pool_alloc_page(git_pool *pool, uint32_t size);
+
+uint32_t git_pool__system_page_size(void)
+{
+ static uint32_t size = 0;
+
+ if (!size) {
+ size_t page_size;
+ if (git__page_size(&page_size) < 0)
+ page_size = 4096;
+ /* allow space for malloc overhead */
+ size = page_size - (2 * sizeof(void *)) - sizeof(git_pool_page);
+ }
+
+ return size;
+}
+
+#ifndef GIT_DEBUG_POOL
+void git_pool_init(git_pool *pool, uint32_t item_size)
+{
+ assert(pool);
+ assert(item_size >= 1);
+
+ memset(pool, 0, sizeof(git_pool));
+ pool->item_size = item_size;
+ pool->page_size = git_pool__system_page_size();
+}
+
+void git_pool_clear(git_pool *pool)
+{
+ git_pool_page *scan, *next;
+
+ for (scan = pool->pages; scan != NULL; scan = next) {
+ next = scan->next;
+ git__free(scan);
+ }
+
+ pool->pages = NULL;
+}
+
+static void *pool_alloc_page(git_pool *pool, uint32_t size)
+{
+ git_pool_page *page;
+ const uint32_t new_page_size = (size <= pool->page_size) ? pool->page_size : size;
+ size_t alloc_size;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) ||
+ !(page = git__malloc(alloc_size)))
+ return NULL;
+
+ page->size = new_page_size;
+ page->avail = new_page_size - size;
+ page->next = pool->pages;
+
+ pool->pages = page;
+
+ return page->data;
+}
+
+static void *pool_alloc(git_pool *pool, uint32_t size)
+{
+ git_pool_page *page = pool->pages;
+ void *ptr = NULL;
+
+ if (!page || page->avail < size)
+ return pool_alloc_page(pool, size);
+
+ ptr = &page->data[page->size - page->avail];
+ page->avail -= size;
+
+ return ptr;
+}
+
+uint32_t git_pool__open_pages(git_pool *pool)
+{
+ uint32_t ct = 0;
+ git_pool_page *scan;
+ for (scan = pool->pages; scan != NULL; scan = scan->next) ct++;
+ return ct;
+}
+
+bool git_pool__ptr_in_pool(git_pool *pool, void *ptr)
+{
+ git_pool_page *scan;
+ for (scan = pool->pages; scan != NULL; scan = scan->next)
+ if ((void *)scan->data <= ptr &&
+ (void *)(((char *)scan->data) + scan->size) > ptr)
+ return true;
+ return false;
+}
+
+#else
+
+static int git_pool__ptr_cmp(const void * a, const void * b)
+{
+ if(a > b) {
+ return 1;
+ }
+ if(a < b) {
+ return -1;
+ }
+ else {
+ return 0;
+ }
+}
+
+void git_pool_init(git_pool *pool, uint32_t item_size)
+{
+ assert(pool);
+ assert(item_size >= 1);
+
+ memset(pool, 0, sizeof(git_pool));
+ pool->item_size = item_size;
+ pool->page_size = git_pool__system_page_size();
+ git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp);
+}
+
+void git_pool_clear(git_pool *pool)
+{
+ git_vector_free_deep(&pool->allocations);
+}
+
+static void *pool_alloc(git_pool *pool, uint32_t size) {
+ void *ptr = NULL;
+ if((ptr = git__malloc(size)) == NULL) {
+ return NULL;
+ }
+ git_vector_insert_sorted(&pool->allocations, ptr, NULL);
+ return ptr;
+}
+
+bool git_pool__ptr_in_pool(git_pool *pool, void *ptr)
+{
+ size_t pos;
+ return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND;
+}
+#endif
+
+void git_pool_swap(git_pool *a, git_pool *b)
+{
+ git_pool temp;
+
+ if (a == b)
+ return;
+
+ memcpy(&temp, a, sizeof(temp));
+ memcpy(a, b, sizeof(temp));
+ memcpy(b, &temp, sizeof(temp));
+}
+
+static uint32_t alloc_size(git_pool *pool, uint32_t count)
+{
+ const uint32_t align = sizeof(void *) - 1;
+
+ if (pool->item_size > 1) {
+ const uint32_t item_size = (pool->item_size + align) & ~align;
+ return item_size * count;
+ }
+
+ return (count + align) & ~align;
+}
+
+void *git_pool_malloc(git_pool *pool, uint32_t items)
+{
+ return pool_alloc(pool, alloc_size(pool, items));
+}
+
+void *git_pool_mallocz(git_pool *pool, uint32_t items)
+{
+ const uint32_t size = alloc_size(pool, items);
+ void *ptr = pool_alloc(pool, size);
+ if (ptr)
+ memset(ptr, 0x0, size);
+ return ptr;
+}
+
+char *git_pool_strndup(git_pool *pool, const char *str, size_t n)
+{
+ char *ptr = NULL;
+
+ assert(pool && str && pool->item_size == sizeof(char));
+
+ if ((uint32_t)(n + 1) < n)
+ return NULL;
+
+ if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) {
+ memcpy(ptr, str, n);
+ ptr[n] = '\0';
+ }
+
+ return ptr;
+}
+
+char *git_pool_strdup(git_pool *pool, const char *str)
+{
+ assert(pool && str && pool->item_size == sizeof(char));
+ return git_pool_strndup(pool, str, strlen(str));
+}
+
+char *git_pool_strdup_safe(git_pool *pool, const char *str)
+{
+ return str ? git_pool_strdup(pool, str) : NULL;
+}
+
+char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
+{
+ void *ptr;
+ size_t len_a, len_b;
+
+ assert(pool && pool->item_size == sizeof(char));
+
+ len_a = a ? strlen(a) : 0;
+ len_b = b ? strlen(b) : 0;
+
+ if ((ptr = git_pool_malloc(pool, (uint32_t)(len_a + len_b + 1))) != NULL) {
+ if (len_a)
+ memcpy(ptr, a, len_a);
+ if (len_b)
+ memcpy(((char *)ptr) + len_a, b, len_b);
+ *(((char *)ptr) + len_a + len_b) = '\0';
+ }
+ return ptr;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_pool_h__
+#define INCLUDE_pool_h__
+
+#include "common.h"
+#include "vector.h"
+
+typedef struct git_pool_page git_pool_page;
+
+#ifndef GIT_DEBUG_POOL
+/**
+ * Chunked allocator.
+ *
+ * A `git_pool` can be used when you want to cheaply allocate
+ * multiple items of the same type and are willing to free them
+ * all together with a single call. The two most common cases
+ * are a set of fixed size items (such as lots of OIDs) or a
+ * bunch of strings.
+ *
+ * Internally, a `git_pool` allocates pages of memory and then
+ * deals out blocks from the trailing unused portion of each page.
+ * The pages guarantee that the number of actual allocations done
+ * will be much smaller than the number of items needed.
+ *
+ * For examples of how to set up a `git_pool` see `git_pool_init`.
+ */
+typedef struct {
+ git_pool_page *pages; /* allocated pages */
+ uint32_t item_size; /* size of single alloc unit in bytes */
+ uint32_t page_size; /* size of page in bytes */
+} git_pool;
+
+#define GIT_POOL_INIT { NULL, 0, 0 }
+
+#else
+
+/**
+ * Debug chunked allocator.
+ *
+ * Acts just like `git_pool` but instead of actually pooling allocations it
+ * passes them through to `git__malloc`. This makes it possible to easily debug
+ * systems that use `git_pool` using valgrind.
+ *
+ * In order to track allocations during the lifetime of the pool we use a
+ * `git_vector`. When the pool is deallocated everything in the vector is
+ * freed.
+ *
+ * `API is exactly the same as the standard `git_pool` with one exception.
+ * Since we aren't allocating pages to hand out in chunks we can't easily
+ * implement `git_pool__open_pages`.
+ */
+typedef struct {
+ git_vector allocations;
+ uint32_t item_size;
+ uint32_t page_size;
+} git_pool;
+
+#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 }
+
+#endif
+
+/**
+ * Initialize a pool.
+ *
+ * To allocation strings, use like this:
+ *
+ * git_pool_init(&string_pool, 1);
+ * my_string = git_pool_strdup(&string_pool, your_string);
+ *
+ * To allocate items of fixed size, use like this:
+ *
+ * git_pool_init(&pool, sizeof(item));
+ * my_item = git_pool_malloc(&pool, 1);
+ *
+ * Of course, you can use this in other ways, but those are the
+ * two most common patterns.
+ */
+extern void git_pool_init(git_pool *pool, uint32_t item_size);
+
+/**
+ * Free all items in pool
+ */
+extern void git_pool_clear(git_pool *pool);
+
+/**
+ * Swap two pools with one another
+ */
+extern void git_pool_swap(git_pool *a, git_pool *b);
+
+/**
+ * Allocate space for one or more items from a pool.
+ */
+extern void *git_pool_malloc(git_pool *pool, uint32_t items);
+extern void *git_pool_mallocz(git_pool *pool, uint32_t items);
+
+/**
+ * Allocate space and duplicate string data into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n);
+
+/**
+ * Allocate space and duplicate a string into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup(git_pool *pool, const char *str);
+
+/**
+ * Allocate space and duplicate a string into it, NULL is no error.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup_safe(git_pool *pool, const char *str);
+
+/**
+ * Allocate space for the concatenation of two strings.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b);
+
+/*
+ * Misc utilities
+ */
+#ifndef GIT_DEBUG_POOL
+extern uint32_t git_pool__open_pages(git_pool *pool);
+#endif
+extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "posix.h"
+#include "path.h"
+#include <stdio.h>
+#include <ctype.h>
+
+#ifndef GIT_WIN32
+
+#ifdef NO_ADDRINFO
+
+int p_getaddrinfo(
+ const char *host,
+ const char *port,
+ struct addrinfo *hints,
+ struct addrinfo **info)
+{
+ struct addrinfo *ainfo, *ai;
+ int p = 0;
+
+ GIT_UNUSED(hints);
+
+ if ((ainfo = malloc(sizeof(struct addrinfo))) == NULL)
+ return -1;
+
+ if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) {
+ free(ainfo);
+ return -2;
+ }
+
+ ainfo->ai_servent = getservbyname(port, 0);
+
+ if (ainfo->ai_servent)
+ ainfo->ai_port = ainfo->ai_servent->s_port;
+ else
+ ainfo->ai_port = atol(port);
+
+ memcpy(&ainfo->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[0],
+ ainfo->ai_hostent->h_length);
+
+ ainfo->ai_protocol = 0;
+ ainfo->ai_socktype = hints->ai_socktype;
+ ainfo->ai_family = ainfo->ai_hostent->h_addrtype;
+ ainfo->ai_addr_in.sin_family = ainfo->ai_family;
+ ainfo->ai_addr_in.sin_port = ainfo->ai_port;
+ ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in;
+ ainfo->ai_addrlen = sizeof(struct sockaddr_in);
+
+ *info = ainfo;
+
+ if (ainfo->ai_hostent->h_addr_list[1] == NULL) {
+ ainfo->ai_next = NULL;
+ return 0;
+ }
+
+ ai = ainfo;
+
+ for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) {
+ if (!(ai->ai_next = malloc(sizeof(struct addrinfo)))) {
+ p_freeaddrinfo(ainfo);
+ return -1;
+ }
+ memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo));
+ memcpy(&ai->ai_next->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[p],
+ ainfo->ai_hostent->h_length);
+ ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in;
+ ai = ai->ai_next;
+ }
+
+ ai->ai_next = NULL;
+ return 0;
+}
+
+void p_freeaddrinfo(struct addrinfo *info)
+{
+ struct addrinfo *p, *next;
+
+ p = info;
+
+ while(p != NULL) {
+ next = p->ai_next;
+ free(p);
+ p = next;
+ }
+}
+
+const char *p_gai_strerror(int ret)
+{
+ switch(ret) {
+ case -1: return "Out of memory"; break;
+ case -2: return "Address lookup failed"; break;
+ default: return "Unknown error"; break;
+ }
+}
+
+#endif /* NO_ADDRINFO */
+
+int p_open(const char *path, volatile int flags, ...)
+{
+ mode_t mode = 0;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ return open(path, flags | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_creat(const char *path, mode_t mode)
+{
+ return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ char *cwd_buffer;
+
+ assert(buffer_out && size > 0);
+
+ cwd_buffer = getcwd(buffer_out, size);
+
+ if (cwd_buffer == NULL)
+ return -1;
+
+ git_path_mkposix(buffer_out);
+ git_path_string_to_dir(buffer_out, size); /* append trailing slash */
+
+ return 0;
+}
+
+int p_rename(const char *from, const char *to)
+{
+ if (!link(from, to)) {
+ p_unlink(from);
+ return 0;
+ }
+
+ if (!rename(from, to))
+ return 0;
+
+ return -1;
+}
+
+#endif /* GIT_WIN32 */
+
+ssize_t p_read(git_file fd, void *buf, size_t cnt)
+{
+ char *b = buf;
+
+ if (!git__is_ssizet(cnt)) {
+#ifdef GIT_WIN32
+ SetLastError(ERROR_INVALID_PARAMETER);
+#endif
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (cnt) {
+ ssize_t r;
+#ifdef GIT_WIN32
+ r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt);
+#else
+ r = read(fd, b, cnt);
+#endif
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ if (!r)
+ break;
+ cnt -= r;
+ b += r;
+ }
+ return (b - (char *)buf);
+}
+
+int p_write(git_file fd, const void *buf, size_t cnt)
+{
+ const char *b = buf;
+
+ while (cnt) {
+ ssize_t r;
+#ifdef GIT_WIN32
+ assert((size_t)((unsigned int)cnt) == cnt);
+ r = write(fd, b, (unsigned int)cnt);
+#else
+ r = write(fd, b, cnt);
+#endif
+ if (r < 0) {
+ if (errno == EINTR || GIT_ISBLOCKED(errno))
+ continue;
+ return -1;
+ }
+ if (!r) {
+ errno = EPIPE;
+ return -1;
+ }
+ cnt -= r;
+ b += r;
+ }
+ return 0;
+}
+
+#ifdef NO_MMAP
+
+#include "map.h"
+
+int git__page_size(size_t *page_size)
+{
+ /* dummy; here we don't need any alignment anyway */
+ *page_size = 4096;
+ return 0;
+}
+
+int git__mmap_alignment(size_t *alignment)
+{
+ /* dummy; here we don't need any alignment anyway */
+ *alignment = 4096;
+ return 0;
+}
+
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
+{
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+
+ if ((prot & GIT_PROT_WRITE) && ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)) {
+ giterr_set(GITERR_OS, "Trying to map shared-writeable");
+ return -1;
+ }
+
+ out->data = malloc(len);
+ GITERR_CHECK_ALLOC(out->data);
+
+ if (!git__is_ssizet(len) ||
+ (p_lseek(fd, offset, SEEK_SET) < 0) ||
+ (p_read(fd, out->data, len) != (ssize_t)len)) {
+ giterr_set(GITERR_OS, "mmap emulation failed");
+ return -1;
+ }
+
+ out->len = len;
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ assert(map != NULL);
+ free(map->data);
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_posix_h__
+#define INCLUDE_posix_h__
+
+#include "common.h"
+#include <fcntl.h>
+#include <time.h>
+#include "fnmatch.h"
+
+/* stat: file mode type testing macros */
+#ifndef S_IFGITLINK
+#define S_IFGITLINK 0160000
+#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
+#endif
+
+#ifndef S_IFLNK
+#define S_IFLNK 0120000
+#undef _S_IFLNK
+#define _S_IFLNK S_IFLNK
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 00100
+#endif
+
+#ifndef S_ISLNK
+#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+#endif
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+#endif
+
+#ifndef S_ISFIFO
+#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
+#endif
+
+/* if S_ISGID is not defined, then don't try to set it */
+#ifndef S_ISGID
+#define S_ISGID 0
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+/* access() mode parameter #defines */
+#ifndef F_OK
+#define F_OK 0 /* existence check */
+#endif
+#ifndef W_OK
+#define W_OK 2 /* write mode check */
+#endif
+#ifndef R_OK
+#define R_OK 4 /* read mode check */
+#endif
+
+/* Determine whether an errno value indicates that a read or write failed
+ * because the descriptor is blocked.
+ */
+#if defined(EWOULDBLOCK)
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK)
+#else
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN)
+#endif
+
+/* define some standard errnos that the runtime may be missing. for example,
+ * mingw lacks EAFNOSUPPORT. */
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT (INT_MAX-1)
+#endif
+
+typedef int git_file;
+
+/**
+ * Standard POSIX Methods
+ *
+ * All the methods starting with the `p_` prefix are
+ * direct ports of the standard POSIX methods.
+ *
+ * Some of the methods are slightly wrapped to provide
+ * saner defaults. Some of these methods are emulated
+ * in Windows platforms.
+ *
+ * Use your manpages to check the docs on these.
+ */
+
+extern ssize_t p_read(git_file fd, void *buf, size_t cnt);
+extern int p_write(git_file fd, const void *buf, size_t cnt);
+
+#define p_close(fd) close(fd)
+#define p_umask(m) umask(m)
+
+extern int p_open(const char *path, int flags, ...);
+extern int p_creat(const char *path, mode_t mode);
+extern int p_getcwd(char *buffer_out, size_t size);
+extern int p_rename(const char *from, const char *to);
+
+extern int git__page_size(size_t *page_size);
+extern int git__mmap_alignment(size_t *page_size);
+
+/**
+ * Platform-dependent methods
+ */
+#ifdef GIT_WIN32
+# include "win32/posix.h"
+#else
+# include "unix/posix.h"
+#endif
+
+#include "strnlen.h"
+
+#ifdef NO_READDIR_R
+GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+ GIT_UNUSED(entry);
+ *result = readdir(dirp);
+ return 0;
+}
+#else /* NO_READDIR_R */
+# define p_readdir_r(d,e,r) readdir_r(d,e,r)
+#endif
+
+#ifdef NO_ADDRINFO
+# include <netdb.h>
+struct addrinfo {
+ struct hostent *ai_hostent;
+ struct servent *ai_servent;
+ struct sockaddr_in ai_addr_in;
+ struct sockaddr *ai_addr;
+ size_t ai_addrlen;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ long ai_port;
+ struct addrinfo *ai_next;
+};
+
+extern int p_getaddrinfo(const char *host, const char *port,
+ struct addrinfo *hints, struct addrinfo **info);
+extern void p_freeaddrinfo(struct addrinfo *info);
+extern const char *p_gai_strerror(int ret);
+#else
+# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d)
+# define p_freeaddrinfo(a) freeaddrinfo(a)
+# define p_gai_strerror(c) gai_strerror(c)
+#endif /* NO_ADDRINFO */
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "pqueue.h"
+#include "util.h"
+
+#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1)
+#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2)
+#define PQUEUE_PARENT_OF(I) (((I)-1)>>1)
+
+int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp)
+{
+ int error = git_vector_init(pq, init_size, cmp);
+
+ if (!error) {
+ /* mix in our flags */
+ pq->flags |= flags;
+
+ /* if fixed size heap, pretend vector is exactly init_size elements */
+ if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0)
+ pq->_alloc_size = init_size;
+ }
+
+ return error;
+}
+
+static void pqueue_up(git_pqueue *pq, size_t el)
+{
+ size_t parent_el = PQUEUE_PARENT_OF(el);
+ void *kid = git_vector_get(pq, el);
+
+ while (el > 0) {
+ void *parent = pq->contents[parent_el];
+
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
+
+ pq->contents[el] = parent;
+
+ el = parent_el;
+ parent_el = PQUEUE_PARENT_OF(el);
+ }
+
+ pq->contents[el] = kid;
+}
+
+static void pqueue_down(git_pqueue *pq, size_t el)
+{
+ void *parent = git_vector_get(pq, el), *kid, *rkid;
+
+ while (1) {
+ size_t kid_el = PQUEUE_LCHILD_OF(el);
+
+ if ((kid = git_vector_get(pq, kid_el)) == NULL)
+ break;
+
+ if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL &&
+ pq->_cmp(kid, rkid) > 0) {
+ kid = rkid;
+ kid_el += 1;
+ }
+
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
+
+ pq->contents[el] = kid;
+ el = kid_el;
+ }
+
+ pq->contents[el] = parent;
+}
+
+int git_pqueue_insert(git_pqueue *pq, void *item)
+{
+ int error = 0;
+
+ /* if heap is full, pop the top element if new one should replace it */
+ if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 &&
+ pq->length >= pq->_alloc_size)
+ {
+ /* skip this item if below min item in heap or if
+ * we do not have a comparison function */
+ if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0)
+ return 0;
+ /* otherwise remove the min item before inserting new */
+ (void)git_pqueue_pop(pq);
+ }
+
+ if (!(error = git_vector_insert(pq, item)) && pq->_cmp)
+ pqueue_up(pq, pq->length - 1);
+
+ return error;
+}
+
+void *git_pqueue_pop(git_pqueue *pq)
+{
+ void *rval;
+
+ if (!pq->_cmp) {
+ rval = git_vector_last(pq);
+ } else {
+ rval = git_pqueue_get(pq, 0);
+ }
+
+ if (git_pqueue_size(pq) > 1 && pq->_cmp) {
+ /* move last item to top of heap, shrink, and push item down */
+ pq->contents[0] = git_vector_last(pq);
+ git_vector_pop(pq);
+ pqueue_down(pq, 0);
+ } else {
+ /* all we need to do is shrink the heap in this case */
+ git_vector_pop(pq);
+ }
+
+ return rval;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_pqueue_h__
+#define INCLUDE_pqueue_h__
+
+#include "vector.h"
+
+typedef git_vector git_pqueue;
+
+enum {
+ /* flag meaning: don't grow heap, keep highest values only */
+ GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1),
+};
+
+/**
+ * Initialize priority queue
+ *
+ * @param pq The priority queue struct to initialize
+ * @param flags Flags (see above) to control queue behavior
+ * @param init_size The initial queue size
+ * @param cmp The entry priority comparison function
+ * @return 0 on success, <0 on error
+ */
+extern int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp);
+
+#define git_pqueue_free git_vector_free
+#define git_pqueue_clear git_vector_clear
+#define git_pqueue_size git_vector_length
+#define git_pqueue_get git_vector_get
+#define git_pqueue_reverse git_vector_reverse
+
+/**
+ * Insert a new item into the queue
+ *
+ * @param pq The priority queue
+ * @param item Pointer to the item data
+ * @return 0 on success, <0 on failure
+ */
+extern int git_pqueue_insert(git_pqueue *pq, void *item);
+
+/**
+ * Remove the top item in the priority queue
+ *
+ * @param pq The priority queue
+ * @return item from heap on success, NULL if queue is empty
+ */
+extern void *git_pqueue_pop(git_pqueue *pq);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/proxy.h"
+
+int git_proxy_init_options(git_proxy_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_proxy_options, GIT_PROXY_OPTIONS_INIT);
+ return 0;
+}
+
+int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src)
+{
+ if (!src) {
+ git_proxy_init_options(tgt, GIT_PROXY_OPTIONS_VERSION);
+ return 0;
+ }
+
+ memcpy(tgt, src, sizeof(git_proxy_options));
+ if (src->url) {
+ tgt->url = git__strdup(src->url);
+ GITERR_CHECK_ALLOC(tgt->url);
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+#ifndef INCLUDE_proxy_h__
+#define INCLUDE_proxy_h__
+
+#include "git2/proxy.h"
+
+extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src);
+
+#endif
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+
+#include "common.h"
+#include "pack.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "vector.h"
+#include "push.h"
+#include "tree.h"
+
+static int push_spec_rref_cmp(const void *a, const void *b)
+{
+ const push_spec *push_spec_a = a, *push_spec_b = b;
+
+ return strcmp(push_spec_a->refspec.dst, push_spec_b->refspec.dst);
+}
+
+static int push_status_ref_cmp(const void *a, const void *b)
+{
+ const push_status *push_status_a = a, *push_status_b = b;
+
+ return strcmp(push_status_a->ref, push_status_b->ref);
+}
+
+int git_push_new(git_push **out, git_remote *remote)
+{
+ git_push *p;
+
+ *out = NULL;
+
+ p = git__calloc(1, sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ p->repo = remote->repo;
+ p->remote = remote;
+ p->report_status = 1;
+ p->pb_parallelism = 1;
+
+ if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) {
+ git__free(p);
+ return -1;
+ }
+
+ if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) {
+ git_vector_free(&p->specs);
+ git__free(p);
+ return -1;
+ }
+
+ if (git_vector_init(&p->updates, 0, NULL) < 0) {
+ git_vector_free(&p->status);
+ git_vector_free(&p->specs);
+ git__free(p);
+ return -1;
+ }
+
+ *out = p;
+ return 0;
+}
+
+int git_push_set_options(git_push *push, const git_push_options *opts)
+{
+ if (!push || !opts)
+ return -1;
+
+ GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options");
+
+ push->pb_parallelism = opts->pb_parallelism;
+ push->custom_headers = &opts->custom_headers;
+
+ return 0;
+}
+
+static void free_refspec(push_spec *spec)
+{
+ if (spec == NULL)
+ return;
+
+ git_refspec__free(&spec->refspec);
+ git__free(spec);
+}
+
+static int check_rref(char *ref)
+{
+ if (git__prefixcmp(ref, "refs/")) {
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_lref(git_push *push, char *ref)
+{
+ /* lref must be resolvable to an existing object */
+ git_object *obj;
+ int error = git_revparse_single(&obj, push->repo, ref);
+ git_object_free(obj);
+
+ if (!error)
+ return 0;
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_REFERENCE,
+ "src refspec '%s' does not match any existing object", ref);
+ else
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+}
+
+static int parse_refspec(git_push *push, push_spec **spec, const char *str)
+{
+ push_spec *s;
+
+ *spec = NULL;
+
+ s = git__calloc(1, sizeof(*s));
+ GITERR_CHECK_ALLOC(s);
+
+ if (git_refspec__parse(&s->refspec, str, false) < 0) {
+ giterr_set(GITERR_INVALID, "invalid refspec %s", str);
+ goto on_error;
+ }
+
+ if (s->refspec.src && s->refspec.src[0] != '\0' &&
+ check_lref(push, s->refspec.src) < 0) {
+ goto on_error;
+ }
+
+ if (check_rref(s->refspec.dst) < 0)
+ goto on_error;
+
+ *spec = s;
+ return 0;
+
+on_error:
+ free_refspec(s);
+ return -1;
+}
+
+int git_push_add_refspec(git_push *push, const char *refspec)
+{
+ push_spec *spec;
+
+ if (parse_refspec(push, &spec, refspec) < 0 ||
+ git_vector_insert(&push->specs, spec) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks)
+{
+ git_buf remote_ref_name = GIT_BUF_INIT;
+ size_t i, j;
+ git_refspec *fetch_spec;
+ push_spec *push_spec = NULL;
+ git_reference *remote_ref;
+ push_status *status;
+ int error = 0;
+
+ git_vector_foreach(&push->status, i, status) {
+ int fire_callback = 1;
+
+ /* Skip unsuccessful updates which have non-empty messages */
+ if (status->msg)
+ continue;
+
+ /* Find the corresponding remote ref */
+ fetch_spec = git_remote__matching_refspec(push->remote, status->ref);
+ if (!fetch_spec)
+ continue;
+
+ if ((error = git_refspec_transform(&remote_ref_name, fetch_spec, status->ref)) < 0)
+ goto on_error;
+
+ /* Find matching push ref spec */
+ git_vector_foreach(&push->specs, j, push_spec) {
+ if (!strcmp(push_spec->refspec.dst, status->ref))
+ break;
+ }
+
+ /* Could not find the corresponding push ref spec for this push update */
+ if (j == push->specs.length)
+ continue;
+
+ /* Update the remote ref */
+ if (git_oid_iszero(&push_spec->loid)) {
+ error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));
+
+ if (error >= 0) {
+ error = git_reference_delete(remote_ref);
+ git_reference_free(remote_ref);
+ }
+ } else {
+ error = git_reference_create(NULL, push->remote->repo,
+ git_buf_cstr(&remote_ref_name), &push_spec->loid, 1,
+ "update by push");
+ }
+
+ if (error < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear();
+ fire_callback = 0;
+ }
+
+ if (fire_callback && callbacks && callbacks->update_tips) {
+ error = callbacks->update_tips(git_buf_cstr(&remote_ref_name),
+ &push_spec->roid, &push_spec->loid, callbacks->payload);
+
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ error = 0;
+
+on_error:
+ git_buf_free(&remote_ref_name);
+ return error;
+}
+
+/**
+ * Insert all tags until we find a non-tag object, which is returned
+ * in `out`.
+ */
+static int enqueue_tag(git_object **out, git_push *push, git_oid *id)
+{
+ git_object *obj = NULL, *target = NULL;
+ int error;
+
+ if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJ_TAG)) < 0)
+ return error;
+
+ while (git_object_type(obj) == GIT_OBJ_TAG) {
+ if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0)
+ break;
+
+ if ((error = git_tag_target(&target, (git_tag *) obj)) < 0)
+ break;
+
+ git_object_free(obj);
+ obj = target;
+ }
+
+ if (error < 0)
+ git_object_free(obj);
+ else
+ *out = obj;
+
+ return error;
+}
+
+static int revwalk(git_vector *commits, git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ git_revwalk *rw;
+ git_oid oid;
+ unsigned int i;
+ int error = -1;
+
+ if (git_revwalk_new(&rw, push->repo) < 0)
+ return -1;
+
+ git_revwalk_sorting(rw, GIT_SORT_TIME);
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_otype type;
+ size_t size;
+
+ if (git_oid_iszero(&spec->loid))
+ /*
+ * Delete reference on remote side;
+ * nothing to do here.
+ */
+ continue;
+
+ if (git_oid_equal(&spec->loid, &spec->roid))
+ continue; /* up-to-date */
+
+ if (git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid) < 0)
+ goto on_error;
+
+ if (type == GIT_OBJ_TAG) {
+ git_object *target;
+
+ if ((error = enqueue_tag(&target, push, &spec->loid)) < 0)
+ goto on_error;
+
+ if (git_object_type(target) == GIT_OBJ_COMMIT) {
+ if (git_revwalk_push(rw, git_object_id(target)) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ } else {
+ if (git_packbuilder_insert(
+ push->pb, git_object_id(target), NULL) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ }
+ git_object_free(target);
+ } else if (git_revwalk_push(rw, &spec->loid) < 0)
+ goto on_error;
+
+ if (!spec->refspec.force) {
+ git_oid base;
+
+ if (git_oid_iszero(&spec->roid))
+ continue;
+
+ if (!git_odb_exists(push->repo->_odb, &spec->roid)) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot push because a reference that you are trying to update on the remote contains commits that are not present locally.");
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ error = git_merge_base(&base, push->repo,
+ &spec->loid, &spec->roid);
+
+ if (error == GIT_ENOTFOUND ||
+ (!error && !git_oid_equal(&base, &spec->roid))) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot push non-fastforwardable reference");
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ if (git_oid_iszero(&head->oid))
+ continue;
+
+ /* TODO */
+ git_revwalk_hide(rw, &head->oid);
+ }
+
+ while ((error = git_revwalk_next(&oid, rw)) == 0) {
+ git_oid *o = git__malloc(GIT_OID_RAWSZ);
+ if (!o) {
+ error = -1;
+ goto on_error;
+ }
+ git_oid_cpy(o, &oid);
+ if ((error = git_vector_insert(commits, o)) < 0)
+ goto on_error;
+ }
+
+on_error:
+ git_revwalk_free(rw);
+ return error == GIT_ITEROVER ? 0 : error;
+}
+
+static int enqueue_object(
+ const git_tree_entry *entry,
+ git_packbuilder *pb)
+{
+ switch (git_tree_entry_type(entry)) {
+ case GIT_OBJ_COMMIT:
+ return 0;
+ case GIT_OBJ_TREE:
+ return git_packbuilder_insert_tree(pb, entry->oid);
+ default:
+ return git_packbuilder_insert(pb, entry->oid, entry->filename);
+ }
+}
+
+static int queue_differences(
+ git_tree *base,
+ git_tree *delta,
+ git_packbuilder *pb)
+{
+ git_tree *b_child = NULL, *d_child = NULL;
+ size_t b_length = git_tree_entrycount(base);
+ size_t d_length = git_tree_entrycount(delta);
+ size_t i = 0, j = 0;
+ int error;
+
+ while (i < b_length && j < d_length) {
+ const git_tree_entry *b_entry = git_tree_entry_byindex(base, i);
+ const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j);
+ int cmp = 0;
+
+ if (!git_oid__cmp(b_entry->oid, d_entry->oid))
+ goto loop;
+
+ cmp = strcmp(b_entry->filename, d_entry->filename);
+
+ /* If the entries are both trees and they have the same name but are
+ * different, then we'll recurse after adding the right-hand entry */
+ if (!cmp &&
+ git_tree_entry__is_tree(b_entry) &&
+ git_tree_entry__is_tree(d_entry)) {
+ /* Add the right-hand entry */
+ if ((error = git_packbuilder_insert(pb, d_entry->oid,
+ d_entry->filename)) < 0)
+ goto on_error;
+
+ /* Acquire the subtrees and recurse */
+ if ((error = git_tree_lookup(&b_child,
+ git_tree_owner(base), b_entry->oid)) < 0 ||
+ (error = git_tree_lookup(&d_child,
+ git_tree_owner(delta), d_entry->oid)) < 0 ||
+ (error = queue_differences(b_child, d_child, pb)) < 0)
+ goto on_error;
+
+ git_tree_free(b_child); b_child = NULL;
+ git_tree_free(d_child); d_child = NULL;
+ }
+ /* If the object is new or different in the right-hand tree,
+ * then enumerate it */
+ else if (cmp >= 0 &&
+ (error = enqueue_object(d_entry, pb)) < 0)
+ goto on_error;
+
+ loop:
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+ }
+
+ /* Drain the right-hand tree of entries */
+ for (; j < d_length; j++)
+ if ((error = enqueue_object(git_tree_entry_byindex(delta, j), pb)) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (b_child)
+ git_tree_free(b_child);
+
+ if (d_child)
+ git_tree_free(d_child);
+
+ return error;
+}
+
+static int queue_objects(git_push *push)
+{
+ git_vector commits = GIT_VECTOR_INIT;
+ git_oid *oid;
+ size_t i;
+ unsigned j;
+ int error;
+
+ if ((error = revwalk(&commits, push)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&commits, i, oid) {
+ git_commit *parent = NULL, *commit;
+ git_tree *tree = NULL, *ptree = NULL;
+ size_t parentcount;
+
+ if ((error = git_commit_lookup(&commit, push->repo, oid)) < 0)
+ goto on_error;
+
+ /* Insert the commit */
+ if ((error = git_packbuilder_insert(push->pb, oid, NULL)) < 0)
+ goto loop_error;
+
+ parentcount = git_commit_parentcount(commit);
+
+ if (!parentcount) {
+ if ((error = git_packbuilder_insert_tree(push->pb,
+ git_commit_tree_id(commit))) < 0)
+ goto loop_error;
+ } else {
+ if ((error = git_tree_lookup(&tree, push->repo,
+ git_commit_tree_id(commit))) < 0 ||
+ (error = git_packbuilder_insert(push->pb,
+ git_commit_tree_id(commit), NULL)) < 0)
+ goto loop_error;
+
+ /* For each parent, add the items which are different */
+ for (j = 0; j < parentcount; j++) {
+ if ((error = git_commit_parent(&parent, commit, j)) < 0 ||
+ (error = git_commit_tree(&ptree, parent)) < 0 ||
+ (error = queue_differences(ptree, tree, push->pb)) < 0)
+ goto loop_error;
+
+ git_tree_free(ptree); ptree = NULL;
+ git_commit_free(parent); parent = NULL;
+ }
+ }
+
+ error = 0;
+
+ loop_error:
+ if (tree)
+ git_tree_free(tree);
+
+ if (ptree)
+ git_tree_free(ptree);
+
+ if (parent)
+ git_commit_free(parent);
+
+ git_commit_free(commit);
+
+ if (error < 0)
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_vector_free_deep(&commits);
+ return error;
+}
+
+static int add_update(git_push *push, push_spec *spec)
+{
+ git_push_update *u = git__calloc(1, sizeof(git_push_update));
+ GITERR_CHECK_ALLOC(u);
+
+ u->src_refname = git__strdup(spec->refspec.src);
+ GITERR_CHECK_ALLOC(u->src_refname);
+
+ u->dst_refname = git__strdup(spec->refspec.dst);
+ GITERR_CHECK_ALLOC(u->dst_refname);
+
+ git_oid_cpy(&u->src, &spec->roid);
+ git_oid_cpy(&u->dst, &spec->loid);
+
+ return git_vector_insert(&push->updates, u);
+}
+
+static int calculate_work(git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j;
+
+ /* Update local and remote oids*/
+
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->refspec.src && spec->refspec.src[0]!= '\0') {
+ /* This is a create or update. Local ref must exist. */
+ if (git_reference_name_to_id(
+ &spec->loid, push->repo, spec->refspec.src) < 0) {
+ giterr_set(GITERR_REFERENCE, "No such reference '%s'", spec->refspec.src);
+ return -1;
+ }
+ }
+
+ /* Remote ref may or may not (e.g. during create) already exist. */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->refspec.dst, head->name)) {
+ git_oid_cpy(&spec->roid, &head->oid);
+ break;
+ }
+ }
+
+ if (add_update(push, spec) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int do_push(git_push *push, const git_remote_callbacks *callbacks)
+{
+ int error = 0;
+ git_transport *transport = push->remote->transport;
+
+ if (!transport->push) {
+ giterr_set(GITERR_NET, "Remote transport doesn't support push");
+ error = -1;
+ goto on_error;
+ }
+
+ /*
+ * A pack-file MUST be sent if either create or update command
+ * is used, even if the server already has all the necessary
+ * objects. In this case the client MUST send an empty pack-file.
+ */
+
+ if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0)
+ goto on_error;
+
+ git_packbuilder_set_threads(push->pb, push->pb_parallelism);
+
+ if (callbacks && callbacks->pack_progress)
+ if ((error = git_packbuilder_set_callbacks(push->pb, callbacks->pack_progress, callbacks->payload)) < 0)
+ goto on_error;
+
+ if ((error = calculate_work(push)) < 0)
+ goto on_error;
+
+ if (callbacks && callbacks->push_negotiation &&
+ (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents,
+ push->updates.length, callbacks->payload)) < 0)
+ goto on_error;
+
+ if ((error = queue_objects(push)) < 0 ||
+ (error = transport->push(transport, push, callbacks)) < 0)
+ goto on_error;
+
+on_error:
+ git_packbuilder_free(push->pb);
+ return error;
+}
+
+static int filter_refs(git_remote *remote)
+{
+ const git_remote_head **heads;
+ size_t heads_len, i;
+
+ git_vector_clear(&remote->refs);
+
+ if (git_remote_ls(&heads, &heads_len, remote) < 0)
+ return -1;
+
+ for (i = 0; i < heads_len; i++) {
+ if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_push_finish(git_push *push, const git_remote_callbacks *callbacks)
+{
+ int error;
+
+ if (!git_remote_connected(push->remote) &&
+ (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks, NULL, push->custom_headers)) < 0)
+ return error;
+
+ if ((error = filter_refs(push->remote)) < 0 ||
+ (error = do_push(push, callbacks)) < 0)
+ return error;
+
+ if (!push->unpack_ok) {
+ error = -1;
+ giterr_set(GITERR_NET, "unpacking the sent packfile failed on the remote");
+ }
+
+ return error;
+}
+
+int git_push_status_foreach(git_push *push,
+ int (*cb)(const char *ref, const char *msg, void *data),
+ void *data)
+{
+ push_status *status;
+ unsigned int i;
+
+ git_vector_foreach(&push->status, i, status) {
+ int error = cb(status->ref, status->msg, data);
+ if (error)
+ return giterr_set_after_callback(error);
+ }
+
+ return 0;
+}
+
+void git_push_status_free(push_status *status)
+{
+ if (status == NULL)
+ return;
+
+ git__free(status->msg);
+ git__free(status->ref);
+ git__free(status);
+}
+
+void git_push_free(git_push *push)
+{
+ push_spec *spec;
+ push_status *status;
+ git_push_update *update;
+ unsigned int i;
+
+ if (push == NULL)
+ return;
+
+ git_vector_foreach(&push->specs, i, spec) {
+ free_refspec(spec);
+ }
+ git_vector_free(&push->specs);
+
+ git_vector_foreach(&push->status, i, status) {
+ git_push_status_free(status);
+ }
+ git_vector_free(&push->status);
+
+ git_vector_foreach(&push->updates, i, update) {
+ git__free(update->src_refname);
+ git__free(update->dst_refname);
+ git__free(update);
+ }
+ git_vector_free(&push->updates);
+
+ git__free(push);
+}
+
+int git_push_init_options(git_push_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_push_h__
+#define INCLUDE_push_h__
+
+#include "git2.h"
+#include "refspec.h"
+
+typedef struct push_spec {
+ struct git_refspec refspec;
+
+ git_oid loid;
+ git_oid roid;
+} push_spec;
+
+typedef struct push_status {
+ bool ok;
+
+ char *ref;
+ char *msg;
+} push_status;
+
+struct git_push {
+ git_repository *repo;
+ git_packbuilder *pb;
+ git_remote *remote;
+ git_vector specs;
+ git_vector updates;
+ bool report_status;
+
+ /* report-status */
+ bool unpack_ok;
+ git_vector status;
+
+ /* options */
+ unsigned pb_parallelism;
+ const git_strarray *custom_headers;
+};
+
+/**
+ * Free the given push status object
+ *
+ * @param status The push status object
+ */
+void git_push_status_free(push_status *status);
+
+/**
+ * Create a new push object
+ *
+ * @param out New push object
+ * @param remote Remote instance
+ *
+ * @return 0 or an error code
+ */
+int git_push_new(git_push **out, git_remote *remote);
+
+/**
+ * Set options on a push object
+ *
+ * @param push The push object
+ * @param opts The options to set on the push object
+ *
+ * @return 0 or an error code
+ */
+int git_push_set_options(
+ git_push *push,
+ const git_push_options *opts);
+
+/**
+ * Add a refspec to be pushed
+ *
+ * @param push The push object
+ * @param refspec Refspec string
+ *
+ * @return 0 or an error code
+ */
+int git_push_add_refspec(git_push *push, const char *refspec);
+
+/**
+ * Update remote tips after a push
+ *
+ * @param push The push object
+ * @param callbacks the callbacks to use for this connection
+ *
+ * @return 0 or an error code
+ */
+int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks);
+
+/**
+ * Perform the push
+ *
+ * This function will return an error in case of a protocol error or
+ * the server being unable to unpack the data we sent.
+ *
+ * The return value does not reflect whether the server accepted or
+ * refused any reference updates. Use `git_push_status_foreach()` in
+ * order to find out which updates were accepted or rejected.
+ *
+ * @param push The push object
+ * @param callbacks the callbacks to use for this connection
+ *
+ * @return 0 or an error code
+ */
+int git_push_finish(git_push *push, const git_remote_callbacks *callbacks);
+
+/**
+ * Invoke callback `cb' on each status entry
+ *
+ * For each of the updated references, we receive a status report in the
+ * form of `ok refs/heads/master` or `ng refs/heads/master <msg>`.
+ * `msg != NULL` means the reference has not been updated for the given
+ * reason.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param push The push object
+ * @param cb The callback to call on each object
+ * @param data The payload passed to the callback
+ *
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+int git_push_status_foreach(git_push *push,
+ int (*cb)(const char *ref, const char *msg, void *data),
+ void *data);
+
+/**
+ * Free the given push object
+ *
+ * @param push The push object
+ */
+void git_push_free(git_push *push);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "buffer.h"
+#include "repository.h"
+#include "posix.h"
+#include "filebuf.h"
+#include "merge.h"
+#include "array.h"
+#include "config.h"
+#include "annotated_commit.h"
+#include "index.h"
+
+#include <git2/types.h>
+#include <git2/annotated_commit.h>
+#include <git2/rebase.h>
+#include <git2/commit.h>
+#include <git2/reset.h>
+#include <git2/revwalk.h>
+#include <git2/notes.h>
+
+#define REBASE_APPLY_DIR "rebase-apply"
+#define REBASE_MERGE_DIR "rebase-merge"
+
+#define HEAD_NAME_FILE "head-name"
+#define ORIG_HEAD_FILE "orig-head"
+#define HEAD_FILE "head"
+#define ONTO_FILE "onto"
+#define ONTO_NAME_FILE "onto_name"
+#define QUIET_FILE "quiet"
+
+#define MSGNUM_FILE "msgnum"
+#define END_FILE "end"
+#define CMT_FILE_FMT "cmt.%" PRIuZ
+#define CURRENT_FILE "current"
+#define REWRITTEN_FILE "rewritten"
+
+#define ORIG_DETACHED_HEAD "detached HEAD"
+
+#define NOTES_DEFAULT_REF NULL
+
+#define REBASE_DIR_MODE 0777
+#define REBASE_FILE_MODE 0666
+
+typedef enum {
+ GIT_REBASE_TYPE_NONE = 0,
+ GIT_REBASE_TYPE_APPLY = 1,
+ GIT_REBASE_TYPE_MERGE = 2,
+ GIT_REBASE_TYPE_INTERACTIVE = 3,
+} git_rebase_type_t;
+
+struct git_rebase {
+ git_repository *repo;
+
+ git_rebase_options options;
+
+ git_rebase_type_t type;
+ char *state_path;
+
+ int head_detached : 1,
+ inmemory : 1,
+ quiet : 1,
+ started : 1;
+
+ git_array_t(git_rebase_operation) operations;
+ size_t current;
+
+ /* Used by in-memory rebase */
+ git_index *index;
+ git_commit *last_commit;
+
+ /* Used by regular (not in-memory) merge-style rebase */
+ git_oid orig_head_id;
+ char *orig_head_name;
+
+ git_oid onto_id;
+ char *onto_name;
+};
+
+#define GIT_REBASE_STATE_INIT {0}
+
+static int rebase_state_type(
+ git_rebase_type_t *type_out,
+ char **path_out,
+ git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_rebase_type_t type = GIT_REBASE_TYPE_NONE;
+
+ if (git_buf_joinpath(&path, repo->path_repository, REBASE_APPLY_DIR) < 0)
+ return -1;
+
+ if (git_path_isdir(git_buf_cstr(&path))) {
+ type = GIT_REBASE_TYPE_APPLY;
+ goto done;
+ }
+
+ git_buf_clear(&path);
+ if (git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR) < 0)
+ return -1;
+
+ if (git_path_isdir(git_buf_cstr(&path))) {
+ type = GIT_REBASE_TYPE_MERGE;
+ goto done;
+ }
+
+done:
+ *type_out = type;
+
+ if (type != GIT_REBASE_TYPE_NONE && path_out)
+ *path_out = git_buf_detach(&path);
+
+ git_buf_free(&path);
+
+ return 0;
+}
+
+GIT_INLINE(int) rebase_readfile(
+ git_buf *out,
+ git_buf *state_path,
+ const char *filename)
+{
+ size_t state_path_len = state_path->size;
+ int error;
+
+ git_buf_clear(out);
+
+ if ((error = git_buf_joinpath(state_path, state_path->ptr, filename)) < 0 ||
+ (error = git_futils_readbuffer(out, state_path->ptr)) < 0)
+ goto done;
+
+ git_buf_rtrim(out);
+
+done:
+ git_buf_truncate(state_path, state_path_len);
+ return error;
+}
+
+GIT_INLINE(int) rebase_readint(
+ size_t *out, git_buf *asc_out, git_buf *state_path, const char *filename)
+{
+ int32_t num;
+ const char *eol;
+ int error = 0;
+
+ if ((error = rebase_readfile(asc_out, state_path, filename)) < 0)
+ return error;
+
+ if (git__strtol32(&num, asc_out->ptr, &eol, 10) < 0 || num < 0 || *eol) {
+ giterr_set(GITERR_REBASE, "The file '%s' contains an invalid numeric value", filename);
+ return -1;
+ }
+
+ *out = (size_t) num;
+
+ return 0;
+}
+
+GIT_INLINE(int) rebase_readoid(
+ git_oid *out, git_buf *str_out, git_buf *state_path, const char *filename)
+{
+ int error;
+
+ if ((error = rebase_readfile(str_out, state_path, filename)) < 0)
+ return error;
+
+ if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) {
+ giterr_set(GITERR_REBASE, "The file '%s' contains an invalid object ID", filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+static git_rebase_operation *rebase_operation_alloc(
+ git_rebase *rebase,
+ git_rebase_operation_t type,
+ git_oid *id,
+ const char *exec)
+{
+ git_rebase_operation *operation;
+
+ assert((type == GIT_REBASE_OPERATION_EXEC) == !id);
+ assert((type == GIT_REBASE_OPERATION_EXEC) == !!exec);
+
+ if ((operation = git_array_alloc(rebase->operations)) == NULL)
+ return NULL;
+
+ operation->type = type;
+ git_oid_cpy((git_oid *)&operation->id, id);
+ operation->exec = exec;
+
+ return operation;
+}
+
+static int rebase_open_merge(git_rebase *rebase)
+{
+ git_buf state_path = GIT_BUF_INIT, buf = GIT_BUF_INIT, cmt = GIT_BUF_INIT;
+ git_oid id;
+ git_rebase_operation *operation;
+ size_t i, msgnum = 0, end;
+ int error;
+
+ if ((error = git_buf_puts(&state_path, rebase->state_path)) < 0)
+ goto done;
+
+ /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */
+ if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto done;
+
+ if (msgnum) {
+ rebase->started = 1;
+ rebase->current = msgnum - 1;
+ }
+
+ /* Read 'end' */
+ if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0)
+ goto done;
+
+ /* Read 'current' if it exists */
+ if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto done;
+
+ /* Read cmt.* */
+ git_array_init_to_size(rebase->operations, end);
+ GITERR_CHECK_ARRAY(rebase->operations);
+
+ for (i = 0; i < end; i++) {
+ git_buf_clear(&cmt);
+
+ if ((error = git_buf_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 ||
+ (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0)
+ goto done;
+
+ operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL);
+ GITERR_CHECK_ALLOC(operation);
+ }
+
+ /* Read 'onto_name' */
+ if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0)
+ goto done;
+
+ rebase->onto_name = git_buf_detach(&buf);
+
+done:
+ git_buf_free(&cmt);
+ git_buf_free(&state_path);
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts)
+{
+ git_rebase *rebase = git__calloc(1, sizeof(git_rebase));
+ GITERR_CHECK_ALLOC(rebase);
+
+ *out = NULL;
+
+ if (rebase_opts)
+ memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options));
+ else
+ git_rebase_init_options(&rebase->options, GIT_REBASE_OPTIONS_VERSION);
+
+ if (rebase_opts && rebase_opts->rewrite_notes_ref) {
+ rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref);
+ GITERR_CHECK_ALLOC(rebase->options.rewrite_notes_ref);
+ }
+
+ if ((rebase->options.checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0)
+ rebase->options.checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ *out = rebase;
+
+ return 0;
+}
+
+static int rebase_check_versions(const git_rebase_options *given_opts)
+{
+ GITERR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options");
+
+ if (given_opts)
+ GITERR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options");
+
+ return 0;
+}
+
+int git_rebase_open(
+ git_rebase **out,
+ git_repository *repo,
+ const git_rebase_options *given_opts)
+{
+ git_rebase *rebase;
+ git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT,
+ orig_head_id = GIT_BUF_INIT, onto_id = GIT_BUF_INIT;
+ int state_path_len, error;
+
+ assert(repo);
+
+ if ((error = rebase_check_versions(given_opts)) < 0)
+ return error;
+
+ if (rebase_alloc(&rebase, given_opts) < 0)
+ return -1;
+
+ rebase->repo = repo;
+
+ if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0)
+ goto done;
+
+ if (rebase->type == GIT_REBASE_TYPE_NONE) {
+ giterr_set(GITERR_REBASE, "There is no rebase in progress");
+ error = GIT_ENOTFOUND;
+ goto done;
+ }
+
+ if ((error = git_buf_puts(&path, rebase->state_path)) < 0)
+ goto done;
+
+ state_path_len = git_buf_len(&path);
+
+ if ((error = git_buf_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 ||
+ (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0)
+ goto done;
+
+ git_buf_rtrim(&orig_head_name);
+
+ if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0)
+ rebase->head_detached = 1;
+
+ git_buf_truncate(&path, state_path_len);
+
+ if ((error = git_buf_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0)
+ goto done;
+
+ if (!git_path_isfile(path.ptr)) {
+ /* Previous versions of git.git used 'head' here; support that. */
+ git_buf_truncate(&path, state_path_len);
+
+ if ((error = git_buf_joinpath(&path, path.ptr, HEAD_FILE)) < 0)
+ goto done;
+ }
+
+ if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0)
+ goto done;
+
+ git_buf_rtrim(&orig_head_id);
+
+ if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0)
+ goto done;
+
+ git_buf_truncate(&path, state_path_len);
+
+ if ((error = git_buf_joinpath(&path, path.ptr, ONTO_FILE)) < 0 ||
+ (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0)
+ goto done;
+
+ git_buf_rtrim(&onto_id);
+
+ if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0)
+ goto done;
+
+ if (!rebase->head_detached)
+ rebase->orig_head_name = git_buf_detach(&orig_head_name);
+
+ switch (rebase->type) {
+ case GIT_REBASE_TYPE_INTERACTIVE:
+ giterr_set(GITERR_REBASE, "Interactive rebase is not supported");
+ error = -1;
+ break;
+ case GIT_REBASE_TYPE_MERGE:
+ error = rebase_open_merge(rebase);
+ break;
+ case GIT_REBASE_TYPE_APPLY:
+ giterr_set(GITERR_REBASE, "Patch application rebase is not supported");
+ error = -1;
+ break;
+ default:
+ abort();
+ }
+
+done:
+ if (error == 0)
+ *out = rebase;
+ else
+ git_rebase_free(rebase);
+
+ git_buf_free(&path);
+ git_buf_free(&orig_head_name);
+ git_buf_free(&orig_head_id);
+ git_buf_free(&onto_id);
+ return error;
+}
+
+static int rebase_cleanup(git_rebase *rebase)
+{
+ if (!rebase || rebase->inmemory)
+ return 0;
+
+ return git_path_isdir(rebase->state_path) ?
+ git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) :
+ 0;
+}
+
+static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...)
+{
+ git_buf path = GIT_BUF_INIT,
+ contents = GIT_BUF_INIT;
+ va_list ap;
+ int error;
+
+ va_start(ap, fmt);
+ git_buf_vprintf(&contents, fmt, ap);
+ va_end(ap);
+
+ if ((error = git_buf_joinpath(&path, rebase->state_path, filename)) == 0)
+ error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE);
+
+ git_buf_free(&path);
+ git_buf_free(&contents);
+
+ return error;
+}
+
+static const char *rebase_onto_name(const git_annotated_commit *onto)
+{
+ if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0)
+ return onto->ref_name + 11;
+ else if (onto->ref_name)
+ return onto->ref_name;
+ else
+ return onto->id_str;
+}
+
+static int rebase_setupfiles_merge(git_rebase *rebase)
+{
+ git_buf commit_filename = GIT_BUF_INIT;
+ char id_str[GIT_OID_HEXSZ];
+ git_rebase_operation *operation;
+ size_t i;
+ int error = 0;
+
+ if ((error = rebase_setupfile(rebase, END_FILE, -1, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 ||
+ (error = rebase_setupfile(rebase, ONTO_NAME_FILE, -1, "%s\n", rebase->onto_name)) < 0)
+ goto done;
+
+ for (i = 0; i < git_array_size(rebase->operations); i++) {
+ operation = git_array_get(rebase->operations, i);
+
+ git_buf_clear(&commit_filename);
+ git_buf_printf(&commit_filename, CMT_FILE_FMT, i+1);
+
+ git_oid_fmt(id_str, &operation->id);
+
+ if ((error = rebase_setupfile(rebase, commit_filename.ptr, -1,
+ "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0)
+ goto done;
+ }
+
+done:
+ git_buf_free(&commit_filename);
+ return error;
+}
+
+static int rebase_setupfiles(git_rebase *rebase)
+{
+ char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ];
+ const char *orig_head_name;
+
+ git_oid_fmt(onto, &rebase->onto_id);
+ git_oid_fmt(orig_head, &rebase->orig_head_id);
+
+ if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) {
+ giterr_set(GITERR_OS, "Failed to create rebase directory '%s'", rebase->state_path);
+ return -1;
+ }
+
+ orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD :
+ rebase->orig_head_name;
+
+ if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 ||
+ rebase_setupfile(rebase, HEAD_NAME_FILE, -1, "%s\n", orig_head_name) < 0 ||
+ rebase_setupfile(rebase, ONTO_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 ||
+ rebase_setupfile(rebase, ORIG_HEAD_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 ||
+ rebase_setupfile(rebase, QUIET_FILE, -1, rebase->quiet ? "t\n" : "\n") < 0)
+ return -1;
+
+ return rebase_setupfiles_merge(rebase);
+}
+
+int git_rebase_init_options(git_rebase_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT);
+ return 0;
+}
+
+static int rebase_ensure_not_in_progress(git_repository *repo)
+{
+ int error;
+ git_rebase_type_t type;
+
+ if ((error = rebase_state_type(&type, NULL, repo)) < 0)
+ return error;
+
+ if (type != GIT_REBASE_TYPE_NONE) {
+ giterr_set(GITERR_REBASE, "There is an existing rebase in progress");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int rebase_ensure_not_dirty(
+ git_repository *repo,
+ bool check_index,
+ bool check_workdir,
+ int fail_with)
+{
+ git_tree *head = NULL;
+ git_index *index = NULL;
+ git_diff *diff = NULL;
+ int error = 0;
+
+ if (check_index) {
+ if ((error = git_repository_head_tree(&head, repo)) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0 ||
+ (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0)
+ goto done;
+
+ if (git_diff_num_deltas(diff) > 0) {
+ giterr_set(GITERR_REBASE, "Uncommitted changes exist in index");
+ error = fail_with;
+ goto done;
+ }
+
+ git_diff_free(diff);
+ diff = NULL;
+ }
+
+ if (check_workdir) {
+ if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0)
+ goto done;
+
+ if (git_diff_num_deltas(diff) > 0) {
+ giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir");
+ error = fail_with;
+ goto done;
+ }
+ }
+
+done:
+ git_diff_free(diff);
+ git_index_free(index);
+ git_tree_free(head);
+
+ return error;
+}
+
+static int rebase_init_operations(
+ git_rebase *rebase,
+ git_repository *repo,
+ const git_annotated_commit *branch,
+ const git_annotated_commit *upstream,
+ const git_annotated_commit *onto)
+{
+ git_revwalk *revwalk = NULL;
+ git_commit *commit;
+ git_oid id;
+ bool merge;
+ git_rebase_operation *operation;
+ int error;
+
+ if (!upstream)
+ upstream = onto;
+
+ if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 ||
+ (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 ||
+ (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0)
+ goto done;
+
+ git_revwalk_sorting(revwalk, GIT_SORT_REVERSE);
+
+ while ((error = git_revwalk_next(&id, revwalk)) == 0) {
+ if ((error = git_commit_lookup(&commit, repo, &id)) < 0)
+ goto done;
+
+ merge = (git_commit_parentcount(commit) > 1);
+ git_commit_free(commit);
+
+ if (merge)
+ continue;
+
+ operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL);
+ GITERR_CHECK_ALLOC(operation);
+ }
+
+ error = 0;
+
+done:
+ git_revwalk_free(revwalk);
+ return error;
+}
+
+static int rebase_init_merge(
+ git_rebase *rebase,
+ git_repository *repo,
+ const git_annotated_commit *branch,
+ const git_annotated_commit *upstream,
+ const git_annotated_commit *onto)
+{
+ git_reference *head_ref = NULL;
+ git_commit *onto_commit = NULL;
+ git_buf reflog = GIT_BUF_INIT;
+ git_buf state_path = GIT_BUF_INIT;
+ int error;
+
+ GIT_UNUSED(upstream);
+
+ if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0)
+ goto done;
+
+ rebase->state_path = git_buf_detach(&state_path);
+ GITERR_CHECK_ALLOC(rebase->state_path);
+
+ if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) {
+ rebase->orig_head_name = git__strdup(branch->ref_name);
+ GITERR_CHECK_ALLOC(rebase->orig_head_name);
+ } else {
+ rebase->head_detached = 1;
+ }
+
+ rebase->onto_name = git__strdup(rebase_onto_name(onto));
+ GITERR_CHECK_ALLOC(rebase->onto_name);
+
+ rebase->quiet = rebase->options.quiet;
+
+ git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch));
+ git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto));
+
+ if ((error = rebase_setupfiles(rebase)) < 0 ||
+ (error = git_buf_printf(&reflog,
+ "rebase: checkout %s", rebase_onto_name(onto))) < 0 ||
+ (error = git_commit_lookup(
+ &onto_commit, repo, git_annotated_commit_id(onto))) < 0 ||
+ (error = git_checkout_tree(repo,
+ (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 ||
+ (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE,
+ git_annotated_commit_id(onto), 1, reflog.ptr)) < 0)
+ goto done;
+
+done:
+ git_reference_free(head_ref);
+ git_commit_free(onto_commit);
+ git_buf_free(&reflog);
+ git_buf_free(&state_path);
+
+ return error;
+}
+
+static int rebase_init_inmemory(
+ git_rebase *rebase,
+ git_repository *repo,
+ const git_annotated_commit *branch,
+ const git_annotated_commit *upstream,
+ const git_annotated_commit *onto)
+{
+ GIT_UNUSED(branch);
+ GIT_UNUSED(upstream);
+
+ return git_commit_lookup(
+ &rebase->last_commit, repo, git_annotated_commit_id(onto));
+}
+
+int git_rebase_init(
+ git_rebase **out,
+ git_repository *repo,
+ const git_annotated_commit *branch,
+ const git_annotated_commit *upstream,
+ const git_annotated_commit *onto,
+ const git_rebase_options *given_opts)
+{
+ git_rebase *rebase = NULL;
+ git_annotated_commit *head_branch = NULL;
+ git_reference *head_ref = NULL;
+ bool inmemory = (given_opts && given_opts->inmemory);
+ int error;
+
+ assert(repo && (upstream || onto));
+
+ *out = NULL;
+
+ if (!onto)
+ onto = upstream;
+
+ if ((error = rebase_check_versions(given_opts)) < 0)
+ goto done;
+
+ if (!inmemory) {
+ if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
+ (error = rebase_ensure_not_in_progress(repo)) < 0 ||
+ (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0)
+ goto done;
+ }
+
+ if (!branch) {
+ if ((error = git_repository_head(&head_ref, repo)) < 0 ||
+ (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0)
+ goto done;
+
+ branch = head_branch;
+ }
+
+ if (rebase_alloc(&rebase, given_opts) < 0)
+ return -1;
+
+ rebase->repo = repo;
+ rebase->inmemory = inmemory;
+ rebase->type = GIT_REBASE_TYPE_MERGE;
+
+ if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0)
+ goto done;
+
+ if (inmemory)
+ error = rebase_init_inmemory(rebase, repo, branch, upstream, onto);
+ else
+ error = rebase_init_merge(rebase, repo, branch ,upstream, onto);
+
+ if (error == 0)
+ *out = rebase;
+
+done:
+ git_reference_free(head_ref);
+ git_annotated_commit_free(head_branch);
+
+ if (error < 0) {
+ rebase_cleanup(rebase);
+ git_rebase_free(rebase);
+ }
+
+ return error;
+}
+
+static void normalize_checkout_options_for_apply(
+ git_checkout_options *checkout_opts,
+ git_rebase *rebase,
+ git_commit *current_commit)
+{
+ memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options));
+
+ if (!checkout_opts->ancestor_label)
+ checkout_opts->ancestor_label = "ancestor";
+
+ if (rebase->type == GIT_REBASE_TYPE_MERGE) {
+ if (!checkout_opts->our_label)
+ checkout_opts->our_label = rebase->onto_name;
+
+ if (!checkout_opts->their_label)
+ checkout_opts->their_label = git_commit_summary(current_commit);
+ } else {
+ abort();
+ }
+}
+
+GIT_INLINE(int) rebase_movenext(git_rebase *rebase)
+{
+ size_t next = rebase->started ? rebase->current + 1 : 0;
+
+ if (next == git_array_size(rebase->operations))
+ return GIT_ITEROVER;
+
+ rebase->started = 1;
+ rebase->current = next;
+
+ return 0;
+}
+
+static int rebase_next_merge(
+ git_rebase_operation **out,
+ git_rebase *rebase)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_commit *current_commit = NULL, *parent_commit = NULL;
+ git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL;
+ git_index *index = NULL;
+ git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
+ git_rebase_operation *operation;
+ git_checkout_options checkout_opts;
+ char current_idstr[GIT_OID_HEXSZ];
+ unsigned int parent_count;
+ int error;
+
+ *out = NULL;
+
+ operation = git_array_get(rebase->operations, rebase->current);
+
+ if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
+ (error = git_commit_tree(¤t_tree, current_commit)) < 0 ||
+ (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0)
+ goto done;
+
+ if ((parent_count = git_commit_parentcount(current_commit)) > 1) {
+ giterr_set(GITERR_REBASE, "Cannot rebase a merge commit");
+ error = -1;
+ goto done;
+ } else if (parent_count) {
+ if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0)
+ goto done;
+ }
+
+ git_oid_fmt(current_idstr, &operation->id);
+
+ normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit);
+
+ if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 ||
+ (error = rebase_setupfile(rebase, MSGNUM_FILE, -1, "%" PRIuZ "\n", rebase->current+1)) < 0 ||
+ (error = rebase_setupfile(rebase, CURRENT_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 ||
+ (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 ||
+ (error = git_merge__check_result(rebase->repo, index)) < 0 ||
+ (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 ||
+ (error = git_indexwriter_commit(&indexwriter)) < 0)
+ goto done;
+
+ *out = operation;
+
+done:
+ git_indexwriter_cleanup(&indexwriter);
+ git_index_free(index);
+ git_tree_free(current_tree);
+ git_tree_free(head_tree);
+ git_tree_free(parent_tree);
+ git_commit_free(parent_commit);
+ git_commit_free(current_commit);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int rebase_next_inmemory(
+ git_rebase_operation **out,
+ git_rebase *rebase)
+{
+ git_commit *current_commit = NULL, *parent_commit = NULL;
+ git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL;
+ git_rebase_operation *operation;
+ git_index *index = NULL;
+ unsigned int parent_count;
+ int error;
+
+ *out = NULL;
+
+ operation = git_array_get(rebase->operations, rebase->current);
+
+ if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
+ (error = git_commit_tree(¤t_tree, current_commit)) < 0)
+ goto done;
+
+ if ((parent_count = git_commit_parentcount(current_commit)) > 1) {
+ giterr_set(GITERR_REBASE, "Cannot rebase a merge commit");
+ error = -1;
+ goto done;
+ } else if (parent_count) {
+ if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0)
+ goto done;
+ }
+
+ if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 ||
+ (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0)
+ goto done;
+
+ if (!rebase->index) {
+ rebase->index = index;
+ index = NULL;
+ } else {
+ if ((error = git_index_read_index(rebase->index, index)) < 0)
+ goto done;
+ }
+
+ *out = operation;
+
+done:
+ git_commit_free(current_commit);
+ git_commit_free(parent_commit);
+ git_tree_free(current_tree);
+ git_tree_free(head_tree);
+ git_tree_free(parent_tree);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_rebase_next(
+ git_rebase_operation **out,
+ git_rebase *rebase)
+{
+ int error;
+
+ assert(out && rebase);
+
+ if ((error = rebase_movenext(rebase)) < 0)
+ return error;
+
+ if (rebase->inmemory)
+ error = rebase_next_inmemory(out, rebase);
+ else if (rebase->type == GIT_REBASE_TYPE_MERGE)
+ error = rebase_next_merge(out, rebase);
+ else
+ abort();
+
+ return error;
+}
+
+int git_rebase_inmemory_index(
+ git_index **out,
+ git_rebase *rebase)
+{
+ assert(out && rebase && rebase->index);
+
+ GIT_REFCOUNT_INC(rebase->index);
+ *out = rebase->index;
+
+ return 0;
+}
+
+static int rebase_commit__create(
+ git_commit **out,
+ git_rebase *rebase,
+ git_index *index,
+ git_commit *parent_commit,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message)
+{
+ git_rebase_operation *operation;
+ git_commit *current_commit = NULL, *commit = NULL;
+ git_tree *parent_tree = NULL, *tree = NULL;
+ git_oid tree_id, commit_id;
+ int error;
+
+ operation = git_array_get(rebase->operations, rebase->current);
+
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_REBASE, "conflicts have not been resolved");
+ error = GIT_EUNMERGED;
+ goto done;
+ }
+
+ if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0 ||
+ (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 ||
+ (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0)
+ goto done;
+
+ if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) {
+ giterr_set(GITERR_REBASE, "this patch has already been applied");
+ error = GIT_EAPPLIED;
+ goto done;
+ }
+
+ if (!author)
+ author = git_commit_author(current_commit);
+
+ if (!message) {
+ message_encoding = git_commit_message_encoding(current_commit);
+ message = git_commit_message(current_commit);
+ }
+
+ if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author,
+ committer, message_encoding, message, tree, 1,
+ (const git_commit **)&parent_commit)) < 0 ||
+ (error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
+ goto done;
+
+ *out = commit;
+
+done:
+ if (error < 0)
+ git_commit_free(commit);
+
+ git_commit_free(current_commit);
+ git_tree_free(parent_tree);
+ git_tree_free(tree);
+
+ return error;
+}
+
+static int rebase_commit_merge(
+ git_oid *commit_id,
+ git_rebase *rebase,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message)
+{
+ git_rebase_operation *operation;
+ git_reference *head = NULL;
+ git_commit *head_commit = NULL, *commit = NULL;
+ git_index *index = NULL;
+ char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ];
+ int error;
+
+ operation = git_array_get(rebase->operations, rebase->current);
+ assert(operation);
+
+ if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 ||
+ (error = git_repository_head(&head, rebase->repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_repository_index(&index, rebase->repo)) < 0 ||
+ (error = rebase_commit__create(&commit, rebase, index, head_commit,
+ author, committer, message_encoding, message)) < 0 ||
+ (error = git_reference__update_for_commit(
+ rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0)
+ goto done;
+
+ git_oid_fmt(old_idstr, &operation->id);
+ git_oid_fmt(new_idstr, git_commit_id(commit));
+
+ if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND,
+ "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0)
+ goto done;
+
+ git_oid_cpy(commit_id, git_commit_id(commit));
+
+done:
+ git_index_free(index);
+ git_reference_free(head);
+ git_commit_free(head_commit);
+ git_commit_free(commit);
+ return error;
+}
+
+static int rebase_commit_inmemory(
+ git_oid *commit_id,
+ git_rebase *rebase,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message)
+{
+ git_commit *commit = NULL;
+ int error = 0;
+
+ assert(rebase->index);
+ assert(rebase->last_commit);
+ assert(rebase->current < rebase->operations.size);
+
+ if ((error = rebase_commit__create(&commit, rebase, rebase->index,
+ rebase->last_commit, author, committer, message_encoding, message)) < 0)
+ goto done;
+
+ git_commit_free(rebase->last_commit);
+ rebase->last_commit = commit;
+
+ git_oid_cpy(commit_id, git_commit_id(commit));
+
+done:
+ if (error < 0)
+ git_commit_free(commit);
+
+ return error;
+}
+
+int git_rebase_commit(
+ git_oid *id,
+ git_rebase *rebase,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message)
+{
+ int error;
+
+ assert(rebase && committer);
+
+ if (rebase->inmemory)
+ error = rebase_commit_inmemory(
+ id, rebase, author, committer, message_encoding, message);
+ else if (rebase->type == GIT_REBASE_TYPE_MERGE)
+ error = rebase_commit_merge(
+ id, rebase, author, committer, message_encoding, message);
+ else
+ abort();
+
+ return error;
+}
+
+int git_rebase_abort(git_rebase *rebase)
+{
+ git_reference *orig_head_ref = NULL;
+ git_commit *orig_head_commit = NULL;
+ int error;
+
+ assert(rebase);
+
+ if (rebase->inmemory)
+ return 0;
+
+ error = rebase->head_detached ?
+ git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE,
+ &rebase->orig_head_id, 1, "rebase: aborting") :
+ git_reference_symbolic_create(
+ &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1,
+ "rebase: aborting");
+
+ if (error < 0)
+ goto done;
+
+ if ((error = git_commit_lookup(
+ &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 ||
+ (error = git_reset(rebase->repo, (git_object *)orig_head_commit,
+ GIT_RESET_HARD, &rebase->options.checkout_options)) < 0)
+ goto done;
+
+ error = rebase_cleanup(rebase);
+
+done:
+ git_commit_free(orig_head_commit);
+ git_reference_free(orig_head_ref);
+
+ return error;
+}
+
+static int notes_ref_lookup(git_buf *out, git_rebase *rebase)
+{
+ git_config *config = NULL;
+ int do_rewrite, error;
+
+ if (rebase->options.rewrite_notes_ref) {
+ git_buf_attach_notowned(out,
+ rebase->options.rewrite_notes_ref,
+ strlen(rebase->options.rewrite_notes_ref));
+ return 0;
+ }
+
+ if ((error = git_repository_config(&config, rebase->repo)) < 0 ||
+ (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) {
+
+ if (error != GIT_ENOTFOUND)
+ goto done;
+
+ giterr_clear();
+ do_rewrite = 1;
+ }
+
+ error = do_rewrite ?
+ git_config_get_string_buf(out, config, "notes.rewriteref") :
+ GIT_ENOTFOUND;
+
+done:
+ git_config_free(config);
+ return error;
+}
+
+static int rebase_copy_note(
+ git_rebase *rebase,
+ const char *notes_ref,
+ git_oid *from,
+ git_oid *to,
+ const git_signature *committer)
+{
+ git_note *note = NULL;
+ git_oid note_id;
+ git_signature *who = NULL;
+ int error;
+
+ if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ goto done;
+ }
+
+ if (!committer) {
+ if((error = git_signature_default(&who, rebase->repo)) < 0) {
+ if (error != GIT_ENOTFOUND ||
+ (error = git_signature_now(&who, "unknown", "unknown")) < 0)
+ goto done;
+
+ giterr_clear();
+ }
+
+ committer = who;
+ }
+
+ error = git_note_create(¬e_id, rebase->repo, notes_ref,
+ git_note_author(note), committer, to, git_note_message(note), 0);
+
+done:
+ git_note_free(note);
+ git_signature_free(who);
+
+ return error;
+}
+
+static int rebase_copy_notes(
+ git_rebase *rebase,
+ const git_signature *committer)
+{
+ git_buf path = GIT_BUF_INIT, rewritten = GIT_BUF_INIT, notes_ref = GIT_BUF_INIT;
+ char *pair_list, *fromstr, *tostr, *end;
+ git_oid from, to;
+ unsigned int linenum = 1;
+ int error = 0;
+
+ if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ goto done;
+ }
+
+ if ((error = git_buf_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 ||
+ (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0)
+ goto done;
+
+ pair_list = rewritten.ptr;
+
+ while (*pair_list) {
+ fromstr = pair_list;
+
+ if ((end = strchr(fromstr, '\n')) == NULL)
+ goto on_error;
+
+ pair_list = end+1;
+ *end = '\0';
+
+ if ((end = strchr(fromstr, ' ')) == NULL)
+ goto on_error;
+
+ tostr = end+1;
+ *end = '\0';
+
+ if (strlen(fromstr) != GIT_OID_HEXSZ ||
+ strlen(tostr) != GIT_OID_HEXSZ ||
+ git_oid_fromstr(&from, fromstr) < 0 ||
+ git_oid_fromstr(&to, tostr) < 0)
+ goto on_error;
+
+ if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0)
+ goto done;
+
+ linenum++;
+ }
+
+ goto done;
+
+on_error:
+ giterr_set(GITERR_REBASE, "Invalid rewritten file at line %d", linenum);
+ error = -1;
+
+done:
+ git_buf_free(&rewritten);
+ git_buf_free(&path);
+ git_buf_free(¬es_ref);
+
+ return error;
+}
+
+static int return_to_orig_head(git_rebase *rebase)
+{
+ git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL;
+ git_commit *terminal_commit = NULL;
+ git_buf branch_msg = GIT_BUF_INIT, head_msg = GIT_BUF_INIT;
+ char onto[GIT_OID_HEXSZ];
+ int error = 0;
+
+ git_oid_fmt(onto, &rebase->onto_id);
+
+ if ((error = git_buf_printf(&branch_msg,
+ "rebase finished: %s onto %.*s",
+ rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 &&
+ (error = git_buf_printf(&head_msg,
+ "rebase finished: returning to %s",
+ rebase->orig_head_name)) == 0 &&
+ (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 &&
+ (error = git_reference_peel((git_object **)&terminal_commit,
+ terminal_ref, GIT_OBJ_COMMIT)) == 0 &&
+ (error = git_reference_create_matching(&branch_ref,
+ rebase->repo, rebase->orig_head_name,
+ git_commit_id(terminal_commit), 1,
+ &rebase->orig_head_id, branch_msg.ptr)) == 0)
+ error = git_reference_symbolic_create(&head_ref,
+ rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1,
+ head_msg.ptr);
+
+ git_buf_free(&head_msg);
+ git_buf_free(&branch_msg);
+ git_commit_free(terminal_commit);
+ git_reference_free(head_ref);
+ git_reference_free(branch_ref);
+ git_reference_free(terminal_ref);
+
+ return error;
+}
+
+int git_rebase_finish(
+ git_rebase *rebase,
+ const git_signature *signature)
+{
+ int error = 0;
+
+ assert(rebase);
+
+ if (rebase->inmemory)
+ return 0;
+
+ if (!rebase->head_detached)
+ error = return_to_orig_head(rebase);
+
+ if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0)
+ error = rebase_cleanup(rebase);
+
+ return error;
+}
+
+size_t git_rebase_operation_entrycount(git_rebase *rebase)
+{
+ assert(rebase);
+
+ return git_array_size(rebase->operations);
+}
+
+size_t git_rebase_operation_current(git_rebase *rebase)
+{
+ assert(rebase);
+
+ return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION;
+}
+
+git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx)
+{
+ assert(rebase);
+
+ return git_array_get(rebase->operations, idx);
+}
+
+void git_rebase_free(git_rebase *rebase)
+{
+ if (rebase == NULL)
+ return;
+
+ git_index_free(rebase->index);
+ git_commit_free(rebase->last_commit);
+ git__free(rebase->onto_name);
+ git__free(rebase->orig_head_name);
+ git__free(rebase->state_path);
+ git_array_clear(rebase->operations);
+ git__free((char *)rebase->options.rewrite_notes_ref);
+ git__free(rebase);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+
+#include "git2/object.h"
+#include "git2/refs.h"
+#include "git2/refdb.h"
+#include "git2/sys/refdb_backend.h"
+
+#include "hash.h"
+#include "refdb.h"
+#include "refs.h"
+#include "reflog.h"
+
+int git_refdb_new(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+
+ assert(out && repo);
+
+ db = git__calloc(1, sizeof(*db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->repo = repo;
+
+ *out = db;
+ GIT_REFCOUNT_INC(db);
+ return 0;
+}
+
+int git_refdb_open(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+ git_refdb_backend *dir;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (git_refdb_new(&db, repo) < 0)
+ return -1;
+
+ /* Add the default (filesystem) backend */
+ if (git_refdb_backend_fs(&dir, repo) < 0) {
+ git_refdb_free(db);
+ return -1;
+ }
+
+ db->repo = repo;
+ db->backend = dir;
+
+ *out = db;
+ return 0;
+}
+
+static void refdb_free_backend(git_refdb *db)
+{
+ if (db->backend)
+ db->backend->free(db->backend);
+}
+
+int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+{
+ refdb_free_backend(db);
+ db->backend = backend;
+
+ return 0;
+}
+
+int git_refdb_compress(git_refdb *db)
+{
+ assert(db);
+
+ if (db->backend->compress)
+ return db->backend->compress(db->backend);
+
+ return 0;
+}
+
+void git_refdb__free(git_refdb *db)
+{
+ refdb_free_backend(db);
+ git__memzero(db, sizeof(*db));
+ git__free(db);
+}
+
+void git_refdb_free(git_refdb *db)
+{
+ if (db == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(db, git_refdb__free);
+}
+
+int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
+{
+ assert(exists && refdb && refdb->backend);
+
+ return refdb->backend->exists(exists, refdb->backend, ref_name);
+}
+
+int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
+{
+ git_reference *ref;
+ int error;
+
+ assert(db && db->backend && out && ref_name);
+
+ error = db->backend->lookup(&ref, db->backend, ref_name);
+ if (error < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
+
+ *out = ref;
+ return 0;
+}
+
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
+{
+ int error;
+
+ if (!db->backend || !db->backend->iterator) {
+ giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators");
+ return -1;
+ }
+
+ if ((error = db->backend->iterator(out, db->backend, glob)) < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+
+ return 0;
+}
+
+int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter)
+{
+ int error;
+
+ if ((error = iter->next(out, iter)) < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(iter->db);
+ (*out)->db = iter->db;
+
+ return 0;
+}
+
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter)
+{
+ return iter->next_name(out, iter);
+}
+
+void git_refdb_iterator_free(git_reference_iterator *iter)
+{
+ GIT_REFCOUNT_DEC(iter->db, git_refdb__free);
+ iter->free(iter);
+}
+
+int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target)
+{
+ assert(db && db->backend);
+
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
+
+ return db->backend->write(db->backend, ref, force, who, message, old_id, old_target);
+}
+
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message)
+{
+ int error;
+
+ assert(db && db->backend);
+ error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message);
+ if (error < 0)
+ return error;
+
+ if (out) {
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+ }
+
+ return 0;
+}
+
+int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target)
+{
+ assert(db && db->backend);
+ return db->backend->del(db->backend, ref_name, old_id, old_target);
+}
+
+int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name)
+{
+ int error;
+
+ assert(db && db->backend);
+
+ if ((error = db->backend->reflog_read(out, db->backend, name)) < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+
+ return 0;
+}
+
+int git_refdb_has_log(git_refdb *db, const char *refname)
+{
+ assert(db && refname);
+
+ return db->backend->has_log(db->backend, refname);
+}
+
+int git_refdb_ensure_log(git_refdb *db, const char *refname)
+{
+ assert(db && refname);
+
+ return db->backend->ensure_log(db->backend, refname);
+}
+
+int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
+ return 0;
+}
+
+int git_refdb_lock(void **payload, git_refdb *db, const char *refname)
+{
+ assert(payload && db && refname);
+
+ if (!db->backend->lock) {
+ giterr_set(GITERR_REFERENCE, "backend does not support locking");
+ return -1;
+ }
+
+ return db->backend->lock(payload, db->backend, refname);
+}
+
+int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message)
+{
+ assert(db);
+
+ return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_h__
+#define INCLUDE_refdb_h__
+
+#include "git2/refdb.h"
+#include "repository.h"
+
+struct git_refdb {
+ git_refcount rc;
+ git_repository *repo;
+ git_refdb_backend *backend;
+};
+
+void git_refdb__free(git_refdb *db);
+
+int git_refdb_exists(
+ int *exists,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_lookup(
+ git_reference **out,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message);
+
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob);
+int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);
+void git_refdb_iterator_free(git_reference_iterator *iter);
+
+int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target);
+int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target);
+
+int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name);
+int git_refdb_reflog_write(git_reflog *reflog);
+
+int git_refdb_has_log(git_refdb *db, const char *refname);
+int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
+
+int git_refdb_lock(void **payload, git_refdb *db, const char *refname);
+int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "refs.h"
+#include "hash.h"
+#include "repository.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "pack.h"
+#include "reflog.h"
+#include "refdb.h"
+#include "refdb_fs.h"
+#include "iterator.h"
+#include "sortedcache.h"
+#include "signature.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/refdb.h>
+#include <git2/branch.h>
+#include <git2/sys/refdb_backend.h>
+#include <git2/sys/refs.h>
+#include <git2/sys/reflog.h>
+
+GIT__USE_STRMAP
+
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
+
+enum {
+ PACKREF_HAS_PEEL = 1,
+ PACKREF_WAS_LOOSE = 2,
+ PACKREF_CANNOT_PEEL = 4,
+ PACKREF_SHADOWED = 8,
+};
+
+enum {
+ PEELING_NONE = 0,
+ PEELING_STANDARD,
+ PEELING_FULL
+};
+
+struct packref {
+ git_oid oid;
+ git_oid peel;
+ char flags;
+ char name[GIT_FLEX_ARRAY];
+};
+
+typedef struct refdb_fs_backend {
+ git_refdb_backend parent;
+
+ git_repository *repo;
+ char *path;
+
+ git_sortedcache *refcache;
+ int peeling_mode;
+ git_iterator_flag_t iterator_flags;
+ uint32_t direach_flags;
+} refdb_fs_backend;
+
+static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
+
+static int packref_cmp(const void *a_, const void *b_)
+{
+ const struct packref *a = a_, *b = b_;
+ return strcmp(a->name, b->name);
+}
+
+static int packed_reload(refdb_fs_backend *backend)
+{
+ int error;
+ git_buf packedrefs = GIT_BUF_INIT;
+ char *scan, *eof, *eol;
+
+ if (!backend->path)
+ return 0;
+
+ error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
+
+ /*
+ * If we can't find the packed-refs, clear table and return.
+ * Any other error just gets passed through.
+ * If no error, and file wasn't changed, just return.
+ * Anything else means we need to refresh the packed refs.
+ */
+ if (error <= 0) {
+ if (error == GIT_ENOTFOUND) {
+ git_sortedcache_clear(backend->refcache, true);
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ /* At this point, refresh the packed refs from the loaded buffer. */
+
+ git_sortedcache_clear(backend->refcache, false);
+
+ scan = (char *)packedrefs.ptr;
+ eof = scan + packedrefs.size;
+
+ backend->peeling_mode = PEELING_NONE;
+
+ if (*scan == '#') {
+ static const char *traits_header = "# pack-refs with: ";
+
+ if (git__prefixcmp(scan, traits_header) == 0) {
+ scan += strlen(traits_header);
+ eol = strchr(scan, '\n');
+
+ if (!eol)
+ goto parse_failed;
+ *eol = '\0';
+
+ if (strstr(scan, " fully-peeled ") != NULL) {
+ backend->peeling_mode = PEELING_FULL;
+ } else if (strstr(scan, " peeled ") != NULL) {
+ backend->peeling_mode = PEELING_STANDARD;
+ }
+
+ scan = eol + 1;
+ }
+ }
+
+ while (scan < eof && *scan == '#') {
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ scan = eol + 1;
+ }
+
+ while (scan < eof) {
+ struct packref *ref;
+ git_oid oid;
+
+ /* parse "<OID> <refname>\n" */
+
+ if (git_oid_fromstr(&oid, scan) < 0)
+ goto parse_failed;
+ scan += GIT_OID_HEXSZ;
+
+ if (*scan++ != ' ')
+ goto parse_failed;
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ *eol = '\0';
+ if (eol[-1] == '\r')
+ eol[-1] = '\0';
+
+ if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
+ goto parse_failed;
+ scan = eol + 1;
+
+ git_oid_cpy(&ref->oid, &oid);
+
+ /* look for optional "^<OID>\n" */
+
+ if (*scan == '^') {
+ if (git_oid_fromstr(&oid, scan + 1) < 0)
+ goto parse_failed;
+ scan += GIT_OID_HEXSZ + 1;
+
+ if (scan < eof) {
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ scan = eol + 1;
+ }
+
+ git_oid_cpy(&ref->peel, &oid);
+ ref->flags |= PACKREF_HAS_PEEL;
+ }
+ else if (backend->peeling_mode == PEELING_FULL ||
+ (backend->peeling_mode == PEELING_STANDARD &&
+ git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0))
+ ref->flags |= PACKREF_CANNOT_PEEL;
+ }
+
+ git_sortedcache_wunlock(backend->refcache);
+ git_buf_free(&packedrefs);
+
+ return 0;
+
+parse_failed:
+ giterr_set(GITERR_REFERENCE, "Corrupted packed references file");
+
+ git_sortedcache_clear(backend->refcache, false);
+ git_sortedcache_wunlock(backend->refcache);
+ git_buf_free(&packedrefs);
+
+ return -1;
+}
+
+static int loose_parse_oid(
+ git_oid *oid, const char *filename, git_buf *file_content)
+{
+ const char *str = git_buf_cstr(file_content);
+
+ if (git_buf_len(file_content) < GIT_OID_HEXSZ)
+ goto corrupted;
+
+ /* we need to get 40 OID characters from the file */
+ if (git_oid_fromstr(oid, str) < 0)
+ goto corrupted;
+
+ /* If the file is longer than 40 chars, the 41st must be a space */
+ str += GIT_OID_HEXSZ;
+ if (*str == '\0' || git__isspace(*str))
+ return 0;
+
+corrupted:
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
+ return -1;
+}
+
+static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
+{
+ int error;
+
+ /* build full path to file */
+ if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
+ (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
+ git_buf_free(buf);
+
+ return error;
+}
+
+static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
+{
+ int error = 0;
+ git_buf ref_file = GIT_BUF_INIT;
+ struct packref *ref = NULL;
+ git_oid oid;
+
+ /* if we fail to load the loose reference, assume someone changed
+ * the filesystem under us and skip it...
+ */
+ if (loose_readbuffer(&ref_file, backend->path, name) < 0) {
+ giterr_clear();
+ goto done;
+ }
+
+ /* skip symbolic refs */
+ if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF))
+ goto done;
+
+ /* parse OID from file */
+ if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0)
+ goto done;
+
+ git_sortedcache_wlock(backend->refcache);
+
+ if (!(error = git_sortedcache_upsert(
+ (void **)&ref, backend->refcache, name))) {
+
+ git_oid_cpy(&ref->oid, &oid);
+ ref->flags = PACKREF_WAS_LOOSE;
+ }
+
+ git_sortedcache_wunlock(backend->refcache);
+
+done:
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int _dirent_loose_load(void *payload, git_buf *full_path)
+{
+ refdb_fs_backend *backend = payload;
+ const char *file_path;
+
+ if (git__suffixcmp(full_path->ptr, ".lock") == 0)
+ return 0;
+
+ if (git_path_isdir(full_path->ptr)) {
+ int error = git_path_direach(
+ full_path, backend->direach_flags, _dirent_loose_load, backend);
+ /* Race with the filesystem, ignore it */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+
+ return error;
+ }
+
+ file_path = full_path->ptr + strlen(backend->path);
+
+ return loose_lookup_to_packfile(backend, file_path);
+}
+
+/*
+ * Load all the loose references from the repository
+ * into the in-memory Packfile, and build a vector with
+ * all the references so it can be written back to
+ * disk.
+ */
+static int packed_loadloose(refdb_fs_backend *backend)
+{
+ int error;
+ git_buf refs_path = GIT_BUF_INIT;
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ /*
+ * Load all the loose files from disk into the Packfile table.
+ * This will overwrite any old packed entries with their
+ * updated loose versions
+ */
+ error = git_path_direach(
+ &refs_path, backend->direach_flags, _dirent_loose_load, backend);
+
+ git_buf_free(&refs_path);
+
+ return error;
+}
+
+static int refdb_fs_backend__exists(
+ int *exists,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_buf ref_path = GIT_BUF_INIT;
+ int error;
+
+ assert(backend);
+
+ if ((error = packed_reload(backend)) < 0 ||
+ (error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
+ return error;
+
+ *exists = git_path_isfile(ref_path.ptr) ||
+ (git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
+
+ git_buf_free(&ref_path);
+ return 0;
+}
+
+static const char *loose_parse_symbolic(git_buf *file_content)
+{
+ const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
+ const char *refname_start;
+
+ refname_start = (const char *)file_content->ptr;
+
+ if (git_buf_len(file_content) < header_len + 1) {
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return NULL;
+ }
+
+ /*
+ * Assume we have already checked for the header
+ * before calling this function
+ */
+ refname_start += header_len;
+
+ return refname_start;
+}
+
+static int loose_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ int error = 0;
+
+ if (out)
+ *out = NULL;
+
+ if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0)
+ /* cannot read loose ref file - gah */;
+ else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
+ const char *target;
+
+ git_buf_rtrim(&ref_file);
+
+ if (!(target = loose_parse_symbolic(&ref_file)))
+ error = -1;
+ else if (out != NULL)
+ *out = git_reference__alloc_symbolic(ref_name, target);
+ } else {
+ git_oid oid;
+
+ if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) &&
+ out != NULL)
+ *out = git_reference__alloc(ref_name, &oid, NULL);
+ }
+
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int ref_error_notfound(const char *name)
+{
+ giterr_set(GITERR_REFERENCE, "Reference '%s' not found", name);
+ return GIT_ENOTFOUND;
+}
+
+static int packed_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ int error = 0;
+ struct packref *entry;
+
+ if ((error = packed_reload(backend)) < 0)
+ return error;
+
+ if (git_sortedcache_rlock(backend->refcache) < 0)
+ return -1;
+
+ entry = git_sortedcache_lookup(backend->refcache, ref_name);
+ if (!entry) {
+ error = ref_error_notfound(ref_name);
+ } else {
+ *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel);
+ if (!*out)
+ error = -1;
+ }
+
+ git_sortedcache_runlock(backend->refcache);
+
+ return error;
+}
+
+static int refdb_fs_backend__lookup(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ int error;
+
+ assert(backend);
+
+ if (!(error = loose_lookup(out, backend, ref_name)))
+ return 0;
+
+ /* only try to lookup this reference on the packfile if it
+ * wasn't found on the loose refs; not if there was a critical error */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = packed_lookup(out, backend, ref_name);
+ }
+
+ return error;
+}
+
+typedef struct {
+ git_reference_iterator parent;
+
+ char *glob;
+
+ git_pool pool;
+ git_vector loose;
+
+ git_sortedcache *cache;
+ size_t loose_pos;
+ size_t packed_pos;
+} refdb_fs_iter;
+
+static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
+{
+ refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
+
+ git_vector_free(&iter->loose);
+ git_pool_clear(&iter->pool);
+ git_sortedcache_free(iter->cache);
+ git__free(iter);
+}
+
+static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ git_iterator *fsit = NULL;
+ git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry = NULL;
+
+ if (!backend->path) /* do nothing if no path for loose refs */
+ return 0;
+
+ fsit_opts.flags = backend->iterator_flags;
+
+ if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
+ (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
+ git_buf_free(&path);
+ return error;
+ }
+
+ error = git_buf_sets(&path, GIT_REFS_DIR);
+
+ while (!error && !git_iterator_advance(&entry, fsit)) {
+ const char *ref_name;
+ struct packref *ref;
+ char *ref_dup;
+
+ git_buf_truncate(&path, strlen(GIT_REFS_DIR));
+ git_buf_puts(&path, entry->path);
+ ref_name = git_buf_cstr(&path);
+
+ if (git__suffixcmp(ref_name, ".lock") == 0 ||
+ (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
+ continue;
+
+ git_sortedcache_rlock(backend->refcache);
+ ref = git_sortedcache_lookup(backend->refcache, ref_name);
+ if (ref)
+ ref->flags |= PACKREF_SHADOWED;
+ git_sortedcache_runlock(backend->refcache);
+
+ ref_dup = git_pool_strdup(&iter->pool, ref_name);
+ if (!ref_dup)
+ error = -1;
+ else
+ error = git_vector_insert(&iter->loose, ref_dup);
+ }
+
+ git_iterator_free(fsit);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator_next(
+ git_reference **out, git_reference_iterator *_iter)
+{
+ int error = GIT_ITEROVER;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ struct packref *ref;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+
+ if (loose_lookup(out, backend, path) == 0)
+ return 0;
+
+ giterr_clear();
+ }
+
+ if (!iter->cache) {
+ if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
+ return error;
+ }
+
+ error = GIT_ITEROVER;
+ while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
+ ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
+ if (!ref) /* stop now if another thread deleted refs and we past end */
+ break;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
+ error = (*out != NULL) ? 0 : -1;
+ break;
+ }
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator_next_name(
+ const char **out, git_reference_iterator *_iter)
+{
+ int error = GIT_ITEROVER;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ struct packref *ref;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+
+ if (loose_lookup(NULL, backend, path) == 0) {
+ *out = path;
+ return 0;
+ }
+
+ giterr_clear();
+ }
+
+ if (!iter->cache) {
+ if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
+ return error;
+ }
+
+ error = GIT_ITEROVER;
+ while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
+ ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
+ if (!ref) /* stop now if another thread deleted refs and we past end */
+ break;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = ref->name;
+ error = 0;
+ break;
+ }
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator(
+ git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
+{
+ int error;
+ refdb_fs_iter *iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ if ((error = packed_reload(backend)) < 0)
+ return error;
+
+ iter = git__calloc(1, sizeof(refdb_fs_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ git_pool_init(&iter->pool, 1);
+
+ if (git_vector_init(&iter->loose, 8, NULL) < 0)
+ goto fail;
+
+ if (glob != NULL &&
+ (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
+ goto fail;
+
+ iter->parent.next = refdb_fs_backend__iterator_next;
+ iter->parent.next_name = refdb_fs_backend__iterator_next_name;
+ iter->parent.free = refdb_fs_backend__iterator_free;
+
+ if (iter_load_loose_paths(backend, iter) < 0)
+ goto fail;
+
+ *out = (git_reference_iterator *)iter;
+ return 0;
+
+fail:
+ refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
+ return -1;
+}
+
+static bool ref_is_available(
+ const char *old_ref, const char *new_ref, const char *this_ref)
+{
+ if (old_ref == NULL || strcmp(old_ref, this_ref)) {
+ size_t reflen = strlen(this_ref);
+ size_t newlen = strlen(new_ref);
+ size_t cmplen = reflen < newlen ? reflen : newlen;
+ const char *lead = reflen < newlen ? new_ref : this_ref;
+
+ if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int reference_path_available(
+ refdb_fs_backend *backend,
+ const char *new_ref,
+ const char* old_ref,
+ int force)
+{
+ size_t i;
+ int error;
+
+ if ((error = packed_reload(backend)) < 0)
+ return error;
+
+ if (!force) {
+ int exists;
+
+ if ((error = refdb_fs_backend__exists(
+ &exists, (git_refdb_backend *)backend, new_ref)) < 0) {
+ return error;
+ }
+
+ if (exists) {
+ giterr_set(GITERR_REFERENCE,
+ "Failed to write reference '%s': a reference with "
+ "that name already exists.", new_ref);
+ return GIT_EEXISTS;
+ }
+ }
+
+ git_sortedcache_rlock(backend->refcache);
+
+ for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(backend->refcache, i);
+
+ if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
+ git_sortedcache_runlock(backend->refcache);
+ giterr_set(GITERR_REFERENCE,
+ "Path to reference '%s' collides with existing one", new_ref);
+ return -1;
+ }
+ }
+
+ git_sortedcache_runlock(backend->refcache);
+ return 0;
+}
+
+static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
+{
+ int error;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ assert(file && backend && name);
+
+ if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
+ giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name);
+ return GIT_EINVALIDSPEC;
+ }
+
+ /* Remove a possibly existing empty directory hierarchy
+ * which name would collide with the reference name
+ */
+ if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
+ return error;
+
+ if (git_buf_joinpath(&ref_path, backend->path, name) < 0)
+ return -1;
+
+ error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
+
+ if (error == GIT_EDIRECTORY)
+ giterr_set(GITERR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);
+
+ git_buf_free(&ref_path);
+ return error;
+}
+
+static int loose_commit(git_filebuf *file, const git_reference *ref)
+{
+ assert(file && ref);
+
+ if (ref->type == GIT_REF_OID) {
+ char oid[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
+
+ git_filebuf_printf(file, "%s\n", oid);
+ } else if (ref->type == GIT_REF_SYMBOLIC) {
+ git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
+ } else {
+ assert(0); /* don't let this happen */
+ }
+
+ return git_filebuf_commit(file);
+}
+
+static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
+{
+ int error;
+ git_filebuf *lock;
+ refdb_fs_backend *backend = (refdb_fs_backend *) _backend;
+
+ lock = git__calloc(1, sizeof(git_filebuf));
+ GITERR_CHECK_ALLOC(lock);
+
+ if ((error = loose_lock(lock, backend, refname)) < 0) {
+ git__free(lock);
+ return error;
+ }
+
+ *out = lock;
+ return 0;
+}
+
+static int refdb_fs_backend__write_tail(
+ git_refdb_backend *_backend,
+ const git_reference *ref,
+ git_filebuf *file,
+ int update_reflog,
+ const git_signature *who,
+ const char *message,
+ const git_oid *old_id,
+ const char *old_target);
+
+static int refdb_fs_backend__delete_tail(
+ git_refdb_backend *_backend,
+ git_filebuf *file,
+ const char *ref_name,
+ const git_oid *old_id, const char *old_target);
+
+static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog,
+ const git_reference *ref, const git_signature *sig, const char *message)
+{
+ git_filebuf *lock = (git_filebuf *) payload;
+ int error = 0;
+
+ if (success == 2)
+ error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL);
+ else if (success)
+ error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL);
+ else
+ git_filebuf_cleanup(lock);
+
+ git__free(lock);
+ return error;
+}
+
+/*
+ * Find out what object this reference resolves to.
+ *
+ * For references that point to a 'big' tag (e.g. an
+ * actual tag object on the repository), we need to
+ * cache on the packfile the OID of the object to
+ * which that 'big tag' is pointing to.
+ */
+static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
+{
+ git_object *object;
+
+ if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
+ return 0;
+
+ /*
+ * Find the tagged object in the repository
+ */
+ if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ /*
+ * If the tagged object is a Tag object, we need to resolve it;
+ * if the ref is actually a 'weak' ref, we don't need to resolve
+ * anything.
+ */
+ if (git_object_type(object) == GIT_OBJ_TAG) {
+ git_tag *tag = (git_tag *)object;
+
+ /*
+ * Find the object pointed at by this tag
+ */
+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));
+ ref->flags |= PACKREF_HAS_PEEL;
+
+ /*
+ * The reference has now cached the resolved OID, and is
+ * marked at such. When written to the packfile, it'll be
+ * accompanied by this resolved oid
+ */
+ }
+
+ git_object_free(object);
+ return 0;
+}
+
+/*
+ * Write a single reference into a packfile
+ */
+static int packed_write_ref(struct packref *ref, git_filebuf *file)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(oid, sizeof(oid), &ref->oid);
+
+ /*
+ * For references that peel to an object in the repo, we must
+ * write the resulting peel on a separate line, e.g.
+ *
+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
+ *
+ * This obviously only applies to tags.
+ * The required peels have already been loaded into `ref->peel_target`.
+ */
+ if (ref->flags & PACKREF_HAS_PEEL) {
+ char peel[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(peel, sizeof(peel), &ref->peel);
+
+ if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
+ return -1;
+ } else {
+ if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Remove all loose references
+ *
+ * Once we have successfully written a packfile,
+ * all the loose references that were packed must be
+ * removed from disk.
+ *
+ * This is a dangerous method; make sure the packfile
+ * is well-written, because we are destructing references
+ * here otherwise.
+ */
+static int packed_remove_loose(refdb_fs_backend *backend)
+{
+ size_t i;
+ git_filebuf lock = GIT_FILEBUF_INIT;
+ git_buf ref_content = GIT_BUF_INIT;
+ int error = 0;
+
+ /* backend->refcache is already locked when this is called */
+
+ for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(backend->refcache, i);
+ git_oid current_id;
+
+ if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
+ continue;
+
+ git_filebuf_cleanup(&lock);
+
+ /* We need to stop anybody from updating the ref while we try to do a safe delete */
+ error = loose_lock(&lock, backend, ref->name);
+ /* If someone else is updating it, let them do it */
+ if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
+ continue;
+
+ if (error < 0) {
+ git_buf_free(&ref_content);
+ giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
+ return error;
+ }
+
+ error = git_futils_readbuffer(&ref_content, lock.path_original);
+ /* Someone else beat us to cleaning up the ref, let's simply continue */
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ /* This became a symref between us packing and trying to delete it, so ignore it */
+ if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF))
+ continue;
+
+ /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
+ if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0)
+ continue;
+
+ /* If the ref moved since we packed it, we must not delete it */
+ if (!git_oid_equal(¤t_id, &ref->oid))
+ continue;
+
+ /*
+ * if we fail to remove a single file, this is *not* good,
+ * but we should keep going and remove as many as possible.
+ * If we fail to remove, the ref is still in the old state, so
+ * we haven't lost information.
+ */
+ p_unlink(lock.path_original);
+ }
+
+ git_buf_free(&ref_content);
+ git_filebuf_cleanup(&lock);
+ return 0;
+}
+
+/*
+ * Write all the contents in the in-memory packfile to disk.
+ */
+static int packed_write(refdb_fs_backend *backend)
+{
+ git_sortedcache *refcache = backend->refcache;
+ git_filebuf pack_file = GIT_FILEBUF_INIT;
+ int error;
+ size_t i;
+
+ /* lock the cache to updates while we do this */
+ if ((error = git_sortedcache_wlock(refcache)) < 0)
+ return error;
+
+ /* Open the file! */
+ if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0)
+ goto fail;
+
+ /* Packfiles have a header... apparently
+ * This is in fact not required, but we might as well print it
+ * just for kicks */
+ if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0)
+ goto fail;
+
+ for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(refcache, i);
+ assert(ref);
+
+ if ((error = packed_find_peel(backend, ref)) < 0)
+ goto fail;
+
+ if ((error = packed_write_ref(ref, &pack_file)) < 0)
+ goto fail;
+ }
+
+ /* if we've written all the references properly, we can commit
+ * the packfile to make the changes effective */
+ if ((error = git_filebuf_commit(&pack_file)) < 0)
+ goto fail;
+
+ /* when and only when the packfile has been properly written,
+ * we can go ahead and remove the loose refs */
+ if ((error = packed_remove_loose(backend)) < 0)
+ goto fail;
+
+ git_sortedcache_updated(refcache);
+ git_sortedcache_wunlock(refcache);
+
+ /* we're good now */
+ return 0;
+
+fail:
+ git_filebuf_cleanup(&pack_file);
+ git_sortedcache_wunlock(refcache);
+
+ return error;
+}
+
+static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
+static int has_reflog(git_repository *repo, const char *name);
+
+/* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */
+static int should_write_reflog(int *write, git_repository *repo, const char *name)
+{
+ int error, logall;
+
+ error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES);
+ if (error < 0)
+ return error;
+
+ /* Defaults to the opposite of the repo being bare */
+ if (logall == GIT_LOGALLREFUPDATES_UNSET)
+ logall = !git_repository_is_bare(repo);
+
+ if (!logall) {
+ *write = 0;
+ } else if (has_reflog(repo, name)) {
+ *write = 1;
+ } else if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR) ||
+ !git__strcmp(name, GIT_HEAD_FILE) ||
+ !git__prefixcmp(name, GIT_REFS_REMOTES_DIR) ||
+ !git__prefixcmp(name, GIT_REFS_NOTES_DIR)) {
+ *write = 1;
+ } else {
+ *write = 0;
+ }
+
+ return 0;
+}
+
+static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
+ const git_oid *old_id, const char *old_target)
+{
+ int error = 0;
+ git_reference *old_ref = NULL;
+
+ *cmp = 0;
+ /* It "matches" if there is no old value to compare against */
+ if (!old_id && !old_target)
+ return 0;
+
+ if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0)
+ goto out;
+
+ /* If the types don't match, there's no way the values do */
+ if (old_id && old_ref->type != GIT_REF_OID) {
+ *cmp = -1;
+ goto out;
+ }
+ if (old_target && old_ref->type != GIT_REF_SYMBOLIC) {
+ *cmp = 1;
+ goto out;
+ }
+
+ if (old_id && old_ref->type == GIT_REF_OID)
+ *cmp = git_oid_cmp(old_id, &old_ref->target.oid);
+
+ if (old_target && old_ref->type == GIT_REF_SYMBOLIC)
+ *cmp = git__strcmp(old_target, old_ref->target.symbolic);
+
+out:
+ git_reference_free(old_ref);
+
+ return error;
+}
+
+/*
+ * The git.git comment regarding this, for your viewing pleasure:
+ *
+ * Special hack: If a branch is updated directly and HEAD
+ * points to it (may happen on the remote side of a push
+ * for example) then logically the HEAD reflog should be
+ * updated too.
+ * A generic solution implies reverse symref information,
+ * but finding all symrefs pointing to the given branch
+ * would be rather costly for this rare event (the direct
+ * update of a branch) to be worth it. So let's cheat and
+ * check with HEAD only which should cover 99% of all usage
+ * scenarios (even 100% of the default ones).
+ */
+static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
+{
+ int error;
+ git_oid old_id = {{0}};
+ git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
+ const char *name;
+
+ if (ref->type == GIT_REF_SYMBOLIC)
+ return 0;
+
+ /* if we can't resolve, we use {0}*40 as old id */
+ git_reference_name_to_id(&old_id, backend->repo, ref->name);
+
+ if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if (git_reference_type(head) == GIT_REF_OID)
+ goto cleanup;
+
+ if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
+ goto cleanup;
+
+ /* Go down the symref chain until we find the branch */
+ while (git_reference_type(tmp) == GIT_REF_SYMBOLIC) {
+ error = git_reference_lookup(&peeled, backend->repo, git_reference_symbolic_target(tmp));
+ if (error < 0)
+ break;
+
+ git_reference_free(tmp);
+ tmp = peeled;
+ }
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ name = git_reference_symbolic_target(tmp);
+ } else if (error < 0) {
+ goto cleanup;
+ } else {
+ name = git_reference_name(tmp);
+ }
+
+ if (strcmp(name, ref->name))
+ goto cleanup;
+
+ error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message);
+
+cleanup:
+ git_reference_free(tmp);
+ git_reference_free(head);
+ return error;
+}
+
+static int refdb_fs_backend__write(
+ git_refdb_backend *_backend,
+ const git_reference *ref,
+ int force,
+ const git_signature *who,
+ const char *message,
+ const git_oid *old_id,
+ const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error = 0;
+
+ assert(backend);
+
+ if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
+ return error;
+
+ /* We need to perform the reflog append and old value check under the ref's lock */
+ if ((error = loose_lock(&file, backend, ref->name)) < 0)
+ return error;
+
+ return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target);
+}
+
+static int refdb_fs_backend__write_tail(
+ git_refdb_backend *_backend,
+ const git_reference *ref,
+ git_filebuf *file,
+ int update_reflog,
+ const git_signature *who,
+ const char *message,
+ const git_oid *old_id,
+ const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ int error = 0, cmp = 0, should_write;
+ const char *new_target = NULL;
+ const git_oid *new_id = NULL;
+
+ if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
+ goto on_error;
+
+ if (cmp) {
+ giterr_set(GITERR_REFERENCE, "old reference value does not match");
+ error = GIT_EMODIFIED;
+ goto on_error;
+ }
+
+ if (ref->type == GIT_REF_SYMBOLIC)
+ new_target = ref->target.symbolic;
+ else
+ new_id = &ref->target.oid;
+
+ error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ /* Don't update if we have the same value */
+ if (!error && !cmp) {
+ error = 0;
+ goto on_error; /* not really error */
+ }
+
+ if (update_reflog) {
+ if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
+ goto on_error;
+
+ if (should_write) {
+ if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
+ goto on_error;
+ if ((error = maybe_append_head(backend, ref, who, message)) < 0)
+ goto on_error;
+ }
+ }
+
+ return loose_commit(file, ref);
+
+on_error:
+ git_filebuf_cleanup(file);
+ return error;
+}
+
+static int refdb_fs_backend__delete(
+ git_refdb_backend *_backend,
+ const char *ref_name,
+ const git_oid *old_id, const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error = 0;
+
+ assert(backend && ref_name);
+
+ if ((error = loose_lock(&file, backend, ref_name)) < 0)
+ return error;
+
+ if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
+ git_filebuf_cleanup(&file);
+ return error;
+ }
+
+ return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
+}
+
+static int refdb_fs_backend__delete_tail(
+ git_refdb_backend *_backend,
+ git_filebuf *file,
+ const char *ref_name,
+ const git_oid *old_id, const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_buf loose_path = GIT_BUF_INIT;
+ size_t pack_pos;
+ int error = 0, cmp = 0;
+ bool loose_deleted = 0;
+
+ error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
+ if (error < 0)
+ goto cleanup;
+
+ if (cmp) {
+ giterr_set(GITERR_REFERENCE, "old reference value does not match");
+ error = GIT_EMODIFIED;
+ goto cleanup;
+ }
+
+ /* If a loose reference exists, remove it from the filesystem */
+ if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
+ return -1;
+
+
+ error = p_unlink(loose_path.ptr);
+ if (error < 0 && errno == ENOENT)
+ error = 0;
+ else if (error < 0)
+ goto cleanup;
+ else if (error == 0)
+ loose_deleted = 1;
+
+ if ((error = packed_reload(backend)) < 0)
+ goto cleanup;
+
+ /* If a packed reference exists, remove it from the packfile and repack */
+ if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
+ goto cleanup;
+
+ if (!(error = git_sortedcache_lookup_index(
+ &pack_pos, backend->refcache, ref_name)))
+ error = git_sortedcache_remove(backend->refcache, pack_pos);
+
+ git_sortedcache_wunlock(backend->refcache);
+
+ if (error == GIT_ENOTFOUND) {
+ error = loose_deleted ? 0 : ref_error_notfound(ref_name);
+ goto cleanup;
+ }
+
+ error = packed_write(backend);
+
+cleanup:
+ git_buf_free(&loose_path);
+ git_filebuf_cleanup(file);
+
+ return error;
+}
+
+static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);
+
+static int refdb_fs_backend__rename(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_reference *old, *new;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error;
+
+ assert(backend);
+
+ if ((error = reference_path_available(
+ backend, new_name, old_name, force)) < 0 ||
+ (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0)
+ return error;
+
+ if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
+ git_reference_free(old);
+ return error;
+ }
+
+ new = git_reference__set_name(old, new_name);
+ if (!new) {
+ git_reference_free(old);
+ return -1;
+ }
+
+ if ((error = loose_lock(&file, backend, new->name)) < 0) {
+ git_reference_free(new);
+ return error;
+ }
+
+ /* Try to rename the refog; it's ok if the old doesn't exist */
+ error = refdb_reflog_fs__rename(_backend, old_name, new_name);
+ if (((error == 0) || (error == GIT_ENOTFOUND)) &&
+ ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) {
+ git_reference_free(new);
+ git_filebuf_cleanup(&file);
+ return error;
+ }
+
+ if (error < 0) {
+ git_reference_free(new);
+ git_filebuf_cleanup(&file);
+ return error;
+ }
+
+
+ if ((error = loose_commit(&file, new)) < 0 || out == NULL) {
+ git_reference_free(new);
+ return error;
+ }
+
+ *out = new;
+ return 0;
+}
+
+static int refdb_fs_backend__compress(git_refdb_backend *_backend)
+{
+ int error;
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */
+ (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */
+ (error = packed_write(backend)) < 0) /* write back to disk */
+ return error;
+
+ return 0;
+}
+
+static void refdb_fs_backend__free(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ git_sortedcache_free(backend->refcache);
+ git__free(backend->path);
+ git__free(backend);
+}
+
+static int setup_namespace(git_buf *path, git_repository *repo)
+{
+ char *parts, *start, *end;
+
+ /* Not all repositories have a path */
+ if (repo->path_repository == NULL)
+ return 0;
+
+ /* Load the path to the repo first */
+ git_buf_puts(path, repo->path_repository);
+
+ /* if the repo is not namespaced, nothing else to do */
+ if (repo->namespace == NULL)
+ return 0;
+
+ parts = end = git__strdup(repo->namespace);
+ if (parts == NULL)
+ return -1;
+
+ /*
+ * From `man gitnamespaces`:
+ * namespaces which include a / will expand to a hierarchy
+ * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
+ * refs under refs/namespaces/foo/refs/namespaces/bar/
+ */
+ while ((start = git__strsep(&end, "/")) != NULL) {
+ git_buf_printf(path, "refs/namespaces/%s/", start);
+ }
+
+ git_buf_printf(path, "refs/namespaces/%s/refs", end);
+ git__free(parts);
+
+ /* Make sure that the folder with the namespace exists */
+ if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository,
+ 0777, GIT_MKDIR_PATH, NULL) < 0)
+ return -1;
+
+ /* Return root of the namespaced path, i.e. without the trailing '/refs' */
+ git_buf_rtruncate_at_char(path, '/');
+ return 0;
+}
+
+static int reflog_alloc(git_reflog **reflog, const char *name)
+{
+ git_reflog *log;
+
+ *reflog = NULL;
+
+ log = git__calloc(1, sizeof(git_reflog));
+ GITERR_CHECK_ALLOC(log);
+
+ log->ref_name = git__strdup(name);
+ GITERR_CHECK_ALLOC(log->ref_name);
+
+ if (git_vector_init(&log->entries, 0, NULL) < 0) {
+ git__free(log->ref_name);
+ git__free(log);
+ return -1;
+ }
+
+ *reflog = log;
+
+ return 0;
+}
+
+static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
+{
+ const char *ptr;
+ git_reflog_entry *entry;
+
+#define seek_forward(_increase) do { \
+ if (_increase >= buf_size) { \
+ giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \
+ goto fail; \
+ } \
+ buf += _increase; \
+ buf_size -= _increase; \
+ } while (0)
+
+ while (buf_size > GIT_REFLOG_SIZE_MIN) {
+ entry = git__calloc(1, sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->committer = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(entry->committer);
+
+ if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0)
+ goto fail;
+ seek_forward(GIT_OID_HEXSZ + 1);
+
+ if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0)
+ goto fail;
+ seek_forward(GIT_OID_HEXSZ + 1);
+
+ ptr = buf;
+
+ /* Seek forward to the end of the signature. */
+ while (*buf && *buf != '\t' && *buf != '\n')
+ seek_forward(1);
+
+ if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0)
+ goto fail;
+
+ if (*buf == '\t') {
+ /* We got a message. Read everything till we reach LF. */
+ seek_forward(1);
+ ptr = buf;
+
+ while (*buf && *buf != '\n')
+ seek_forward(1);
+
+ entry->msg = git__strndup(ptr, buf - ptr);
+ GITERR_CHECK_ALLOC(entry->msg);
+ } else
+ entry->msg = NULL;
+
+ while (*buf && *buf == '\n' && buf_size > 1)
+ seek_forward(1);
+
+ if (git_vector_insert(&log->entries, entry) < 0)
+ goto fail;
+ }
+
+ return 0;
+
+#undef seek_forward
+
+fail:
+ git_reflog_entry__free(entry);
+
+ return -1;
+}
+
+static int create_new_reflog_file(const char *filepath)
+{
+ int fd, error;
+
+ if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
+ return error;
+
+ if ((fd = p_open(filepath,
+ O_WRONLY | O_CREAT,
+ GIT_REFLOG_FILE_MODE)) < 0)
+ return -1;
+
+ return p_close(fd);
+}
+
+GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
+{
+ return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
+}
+
+static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
+{
+ refdb_fs_backend *backend;
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
+ return error;
+
+ error = create_new_reflog_file(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int has_reflog(git_repository *repo, const char *name)
+{
+ int ret = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if (retrieve_reflog_path(&path, repo, name) < 0)
+ goto cleanup;
+
+ ret = git_path_isfile(git_buf_cstr(&path));
+
+cleanup:
+ git_buf_free(&path);
+ return ret;
+}
+
+static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+
+ return has_reflog(backend->repo, name);
+}
+
+static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name)
+{
+ int error = -1;
+ git_buf log_path = GIT_BUF_INIT;
+ git_buf log_file = GIT_BUF_INIT;
+ git_reflog *log = NULL;
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(out && _backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if (reflog_alloc(&log, name) < 0)
+ return -1;
+
+ if (retrieve_reflog_path(&log_path, repo, name) < 0)
+ goto cleanup;
+
+ error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if ((error == GIT_ENOTFOUND) &&
+ ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
+ goto cleanup;
+
+ if ((error = reflog_parse(log,
+ git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
+ goto cleanup;
+
+ *out = log;
+ goto success;
+
+cleanup:
+ git_reflog_free(log);
+
+success:
+ git_buf_free(&log_file);
+ git_buf_free(&log_path);
+
+ return error;
+}
+
+static int serialize_reflog_entry(
+ git_buf *buf,
+ const git_oid *oid_old,
+ const git_oid *oid_new,
+ const git_signature *committer,
+ const char *msg)
+{
+ char raw_old[GIT_OID_HEXSZ+1];
+ char raw_new[GIT_OID_HEXSZ+1];
+
+ git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
+ git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
+
+ git_buf_clear(buf);
+
+ git_buf_puts(buf, raw_old);
+ git_buf_putc(buf, ' ');
+ git_buf_puts(buf, raw_new);
+
+ git_signature__writebuf(buf, " ", committer);
+
+ /* drop trailing LF */
+ git_buf_rtrim(buf);
+
+ if (msg) {
+ git_buf_putc(buf, '\t');
+ git_buf_puts(buf, msg);
+ }
+
+ git_buf_putc(buf, '\n');
+
+ return git_buf_oom(buf);
+}
+
+static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname)
+{
+ git_repository *repo;
+ git_buf log_path = GIT_BUF_INIT;
+ int error;
+
+ repo = backend->repo;
+
+ if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
+ giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname);
+ return GIT_EINVALIDSPEC;
+ }
+
+ if (retrieve_reflog_path(&log_path, repo, refname) < 0)
+ return -1;
+
+ if (!git_path_isfile(git_buf_cstr(&log_path))) {
+ giterr_set(GITERR_INVALID,
+ "Log file for reference '%s' doesn't exist.", refname);
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_filebuf_open(file, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
+
+cleanup:
+ git_buf_free(&log_path);
+
+ return error;
+}
+
+static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog)
+{
+ int error = -1;
+ unsigned int i;
+ git_reflog_entry *entry;
+ refdb_fs_backend *backend;
+ git_buf log = GIT_BUF_INIT;
+ git_filebuf fbuf = GIT_FILEBUF_INIT;
+
+ assert(_backend && reflog);
+
+ backend = (refdb_fs_backend *) _backend;
+
+ if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
+ return -1;
+
+ git_vector_foreach(&reflog->entries, i, entry) {
+ if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&fbuf);
+ goto success;
+
+cleanup:
+ git_filebuf_cleanup(&fbuf);
+
+success:
+ git_buf_free(&log);
+
+ return error;
+}
+
+/* Append to the reflog, must be called under reference lock */
+static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message)
+{
+ int error, is_symbolic;
+ git_oid old_id = {{0}}, new_id = {{0}};
+ git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
+ git_repository *repo = backend->repo;
+
+ is_symbolic = ref->type == GIT_REF_SYMBOLIC;
+
+ /* "normal" symbolic updates do not write */
+ if (is_symbolic &&
+ strcmp(ref->name, GIT_HEAD_FILE) &&
+ !(old && new))
+ return 0;
+
+ /* From here on is_symoblic also means that it's HEAD */
+
+ if (old) {
+ git_oid_cpy(&old_id, old);
+ } else {
+ error = git_reference_name_to_id(&old_id, repo, ref->name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ }
+
+ if (new) {
+ git_oid_cpy(&new_id, new);
+ } else {
+ if (!is_symbolic) {
+ git_oid_cpy(&new_id, git_reference_target(ref));
+ } else {
+ error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ /* detaching HEAD does not create an entry */
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ giterr_clear();
+ }
+ }
+
+ if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
+ goto cleanup;
+
+ if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
+ (error != GIT_EEXISTS)) {
+ goto cleanup;
+ }
+
+ /* If the new branch matches part of the namespace of a previously deleted branch,
+ * there maybe an obsolete/unused directory (or directory hierarchy) in the way.
+ */
+ if (git_path_isdir(git_buf_cstr(&path))) {
+ if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ } else if (git_path_isdir(git_buf_cstr(&path))) {
+ giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
+ ref->name);
+ error = GIT_EDIRECTORY;
+ }
+
+ if (error != 0)
+ goto cleanup;
+ }
+
+ error = git_futils_writebuffer(&buf, git_buf_cstr(&path), O_WRONLY|O_CREAT|O_APPEND, GIT_REFLOG_FILE_MODE);
+
+cleanup:
+ git_buf_free(&buf);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
+{
+ int error = 0, fd;
+ git_buf old_path = GIT_BUF_INIT;
+ git_buf new_path = GIT_BUF_INIT;
+ git_buf temp_path = GIT_BUF_INIT;
+ git_buf normalized = GIT_BUF_INIT;
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(_backend && old_name && new_name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if ((error = git_reference__normalize_name(
+ &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
+ return error;
+
+ if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
+ return -1;
+
+ if (!git_path_exists(git_buf_cstr(&old_path))) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ /*
+ * Move the reflog to a temporary place. This two-phase renaming is required
+ * in order to cope with funny renaming use cases when one tries to move a reference
+ * to a partially colliding namespace:
+ * - a/b -> a/b/c
+ * - a/b/c/d -> a/b/c
+ */
+ if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
+ return -1;
+
+ if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ p_close(fd);
+
+ if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_path_isdir(git_buf_cstr(&new_path)) &&
+ (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+ error = -1;
+ }
+
+cleanup:
+ git_buf_free(&temp_path);
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+ git_buf_free(&normalized);
+
+ return error;
+}
+
+static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ error = retrieve_reflog_path(&path, repo, name);
+
+ if (!error && git_path_exists(path.ptr))
+ error = p_unlink(path.ptr);
+
+ git_buf_free(&path);
+
+ return error;
+
+}
+
+int git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repository)
+{
+ int t = 0;
+ git_buf path = GIT_BUF_INIT;
+ refdb_fs_backend *backend;
+
+ backend = git__calloc(1, sizeof(refdb_fs_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->repo = repository;
+
+ if (setup_namespace(&path, repository) < 0)
+ goto fail;
+
+ backend->path = git_buf_detach(&path);
+
+ if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 ||
+ git_sortedcache_new(
+ &backend->refcache, offsetof(struct packref, name),
+ NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0)
+ goto fail;
+
+ git_buf_free(&path);
+
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
+ backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE;
+ }
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+ backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
+ }
+
+ backend->parent.exists = &refdb_fs_backend__exists;
+ backend->parent.lookup = &refdb_fs_backend__lookup;
+ backend->parent.iterator = &refdb_fs_backend__iterator;
+ backend->parent.write = &refdb_fs_backend__write;
+ backend->parent.del = &refdb_fs_backend__delete;
+ backend->parent.rename = &refdb_fs_backend__rename;
+ backend->parent.compress = &refdb_fs_backend__compress;
+ backend->parent.lock = &refdb_fs_backend__lock;
+ backend->parent.unlock = &refdb_fs_backend__unlock;
+ backend->parent.has_log = &refdb_reflog_fs__has_log;
+ backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
+ backend->parent.free = &refdb_fs_backend__free;
+ backend->parent.reflog_read = &refdb_reflog_fs__read;
+ backend->parent.reflog_write = &refdb_reflog_fs__write;
+ backend->parent.reflog_rename = &refdb_reflog_fs__rename;
+ backend->parent.reflog_delete = &refdb_reflog_fs__delete;
+
+ *backend_out = (git_refdb_backend *)backend;
+ return 0;
+
+fail:
+ git_buf_free(&path);
+ git__free(backend->path);
+ git__free(backend);
+ return -1;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_fs_h__
+#define INCLUDE_refdb_fs_h__
+
+typedef struct {
+ git_strmap *packfile;
+ time_t packfile_time;
+} git_refcache;
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "reflog.h"
+#include "repository.h"
+#include "filebuf.h"
+#include "signature.h"
+#include "refdb.h"
+
+#include <git2/sys/refdb_backend.h>
+
+git_reflog_entry *git_reflog_entry__alloc(void)
+{
+ return git__calloc(1, sizeof(git_reflog_entry));
+}
+
+void git_reflog_entry__free(git_reflog_entry *entry)
+{
+ git_signature_free(entry->committer);
+
+ git__free(entry->msg);
+ git__free(entry);
+}
+
+void git_reflog_free(git_reflog *reflog)
+{
+ size_t i;
+ git_reflog_entry *entry;
+
+ if (reflog == NULL)
+ return;
+
+ if (reflog->db)
+ GIT_REFCOUNT_DEC(reflog->db, git_refdb__free);
+
+ for (i=0; i < reflog->entries.length; i++) {
+ entry = git_vector_get(&reflog->entries, i);
+
+ git_reflog_entry__free(entry);
+ }
+
+ git_vector_free(&reflog->entries);
+ git__free(reflog->ref_name);
+ git__free(reflog);
+}
+
+int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name)
+{
+ git_refdb *refdb;
+ int error;
+
+ assert(reflog && repo && name);
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ return git_refdb_reflog_read(reflog, refdb, name);
+}
+
+int git_reflog_write(git_reflog *reflog)
+{
+ git_refdb *db;
+
+ assert(reflog && reflog->db);
+
+ db = reflog->db;
+ return db->backend->reflog_write(db->backend, reflog);
+}
+
+int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg)
+{
+ git_reflog_entry *entry;
+ const git_reflog_entry *previous;
+ const char *newline;
+
+ assert(reflog && new_oid && committer);
+
+ entry = git__calloc(1, sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if ((git_signature_dup(&entry->committer, committer)) < 0)
+ goto cleanup;
+
+ if (msg != NULL) {
+ if ((entry->msg = git__strdup(msg)) == NULL)
+ goto cleanup;
+
+ newline = strchr(msg, '\n');
+
+ if (newline) {
+ if (newline[1] != '\0') {
+ giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
+ goto cleanup;
+ }
+
+ entry->msg[newline - msg] = '\0';
+ }
+ }
+
+ previous = git_reflog_entry_byindex(reflog, 0);
+
+ if (previous == NULL)
+ git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
+ else
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
+
+ git_oid_cpy(&entry->oid_cur, new_oid);
+
+ if (git_vector_insert(&reflog->entries, entry) < 0)
+ goto cleanup;
+
+ return 0;
+
+cleanup:
+ git_reflog_entry__free(entry);
+ return -1;
+}
+
+int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name)
+{
+ git_refdb *refdb;
+ int error;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
+
+ return refdb->backend->reflog_rename(refdb->backend, old_name, new_name);
+}
+
+int git_reflog_delete(git_repository *repo, const char *name)
+{
+ git_refdb *refdb;
+ int error;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
+
+ return refdb->backend->reflog_delete(refdb->backend, name);
+}
+
+size_t git_reflog_entrycount(git_reflog *reflog)
+{
+ assert(reflog);
+ return reflog->entries.length;
+}
+
+const git_reflog_entry * git_reflog_entry_byindex(const git_reflog *reflog, size_t idx)
+{
+ assert(reflog);
+
+ if (idx >= reflog->entries.length)
+ return NULL;
+
+ return git_vector_get(
+ &reflog->entries, reflog_inverse_index(idx, reflog->entries.length));
+}
+
+const git_oid * git_reflog_entry_id_old(const git_reflog_entry *entry)
+{
+ assert(entry);
+ return &entry->oid_old;
+}
+
+const git_oid * git_reflog_entry_id_new(const git_reflog_entry *entry)
+{
+ assert(entry);
+ return &entry->oid_cur;
+}
+
+const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
+{
+ assert(entry);
+ return entry->committer;
+}
+
+const char * git_reflog_entry_message(const git_reflog_entry *entry)
+{
+ assert(entry);
+ return entry->msg;
+}
+
+int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry)
+{
+ size_t entrycount;
+ git_reflog_entry *entry, *previous;
+
+ entrycount = git_reflog_entrycount(reflog);
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+
+ if (entry == NULL) {
+ giterr_set(GITERR_REFERENCE, "No reflog entry at index %"PRIuZ, idx);
+ return GIT_ENOTFOUND;
+ }
+
+ git_reflog_entry__free(entry);
+
+ if (git_vector_remove(
+ &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0)
+ return -1;
+
+ if (!rewrite_previous_entry)
+ return 0;
+
+ /* No need to rewrite anything when removing the most recent entry */
+ if (idx == 0)
+ return 0;
+
+ /* Have the latest entry just been dropped? */
+ if (entrycount == 1)
+ return 0;
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
+
+ /* If the oldest entry has just been removed... */
+ if (idx == entrycount - 1) {
+ /* ...clear the oid_old member of the "new" oldest entry */
+ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
+ return -1;
+
+ return 0;
+ }
+
+ previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_reflog_h__
+#define INCLUDE_reflog_h__
+
+#include "common.h"
+#include "git2/reflog.h"
+#include "vector.h"
+
+#define GIT_REFLOG_DIR "logs/"
+#define GIT_REFLOG_DIR_MODE 0777
+#define GIT_REFLOG_FILE_MODE 0666
+
+#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
+
+struct git_reflog_entry {
+ git_oid oid_old;
+ git_oid oid_cur;
+
+ git_signature *committer;
+
+ char *msg;
+};
+
+struct git_reflog {
+ git_refdb *db;
+ char *ref_name;
+ git_vector entries;
+};
+
+GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total)
+{
+ return (total - 1) - idx;
+}
+
+#endif /* INCLUDE_reflog_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "refs.h"
+#include "hash.h"
+#include "repository.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "pack.h"
+#include "reflog.h"
+#include "refdb.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/oid.h>
+#include <git2/branch.h>
+#include <git2/refs.h>
+#include <git2/refdb.h>
+#include <git2/sys/refs.h>
+#include <git2/signature.h>
+#include <git2/commit.h>
+
+GIT__USE_STRMAP
+
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
+
+enum {
+ GIT_PACKREF_HAS_PEEL = 1,
+ GIT_PACKREF_WAS_LOOSE = 2
+};
+
+static git_reference *alloc_ref(const char *name)
+{
+ git_reference *ref = NULL;
+ size_t namelen = strlen(name), reflen;
+
+ if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
+ !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
+ (ref = git__calloc(1, reflen)) != NULL)
+ memcpy(ref->name, name, namelen + 1);
+
+ return ref;
+}
+
+git_reference *git_reference__alloc_symbolic(
+ const char *name, const char *target)
+{
+ git_reference *ref;
+
+ assert(name && target);
+
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
+
+ ref->type = GIT_REF_SYMBOLIC;
+
+ if ((ref->target.symbolic = git__strdup(target)) == NULL) {
+ git__free(ref);
+ return NULL;
+ }
+
+ return ref;
+}
+
+git_reference *git_reference__alloc(
+ const char *name,
+ const git_oid *oid,
+ const git_oid *peel)
+{
+ git_reference *ref;
+
+ assert(name && oid);
+
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
+
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
+
+ if (peel != NULL)
+ git_oid_cpy(&ref->peel, peel);
+
+ return ref;
+}
+
+git_reference *git_reference__set_name(
+ git_reference *ref, const char *name)
+{
+ size_t namelen = strlen(name);
+ size_t reflen;
+ git_reference *rewrite = NULL;
+
+ if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
+ !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
+ (rewrite = git__realloc(ref, reflen)) != NULL)
+ memcpy(rewrite->name, name, namelen + 1);
+
+ return rewrite;
+}
+
+int git_reference_dup(git_reference **dest, git_reference *source)
+{
+ if (source->type == GIT_REF_SYMBOLIC)
+ *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic);
+ else
+ *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel);
+
+ GITERR_CHECK_ALLOC(*dest);
+
+ return 0;
+}
+
+void git_reference_free(git_reference *reference)
+{
+ if (reference == NULL)
+ return;
+
+ if (reference->type == GIT_REF_SYMBOLIC)
+ git__free(reference->target.symbolic);
+
+ if (reference->db)
+ GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
+
+ git__free(reference);
+}
+
+int git_reference_delete(git_reference *ref)
+{
+ const git_oid *old_id = NULL;
+ const char *old_target = NULL;
+
+ if (ref->type == GIT_REF_OID)
+ old_id = &ref->target.oid;
+ else
+ old_target = ref->target.symbolic;
+
+ return git_refdb_delete(ref->db, ref->name, old_id, old_target);
+}
+
+int git_reference_remove(git_repository *repo, const char *name)
+{
+ git_refdb *db;
+ int error;
+
+ if ((error = git_repository_refdb__weakptr(&db, repo)) < 0)
+ return error;
+
+ return git_refdb_delete(db, name, NULL, NULL);
+}
+
+int git_reference_lookup(git_reference **ref_out,
+ git_repository *repo, const char *name)
+{
+ return git_reference_lookup_resolved(ref_out, repo, name, 0);
+}
+
+int git_reference_name_to_id(
+ git_oid *out, git_repository *repo, const char *name)
+{
+ int error;
+ git_reference *ref;
+
+ if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
+ return error;
+
+ git_oid_cpy(out, git_reference_target(ref));
+ git_reference_free(ref);
+ return 0;
+}
+
+static int reference_normalize_for_repo(
+ git_refname_t out,
+ git_repository *repo,
+ const char *name)
+{
+ int precompose;
+ unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL;
+
+ if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) &&
+ precompose)
+ flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;
+
+ return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
+}
+
+int git_reference_lookup_resolved(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ int max_nesting)
+{
+ git_refname_t scan_name;
+ git_ref_t scan_type;
+ int error = 0, nesting;
+ git_reference *ref = NULL;
+ git_refdb *refdb;
+
+ assert(ref_out && repo && name);
+
+ *ref_out = NULL;
+
+ if (max_nesting > MAX_NESTING_LEVEL)
+ max_nesting = MAX_NESTING_LEVEL;
+ else if (max_nesting < 0)
+ max_nesting = DEFAULT_NESTING_LEVEL;
+
+ scan_type = GIT_REF_SYMBOLIC;
+
+ if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0)
+ return error;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ for (nesting = max_nesting;
+ nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
+ nesting--)
+ {
+ if (nesting != max_nesting) {
+ strncpy(scan_name, ref->target.symbolic, sizeof(scan_name));
+ git_reference_free(ref);
+ }
+
+ if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
+ return error;
+
+ scan_type = ref->type;
+ }
+
+ if (scan_type != GIT_REF_OID && max_nesting != 0) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot resolve reference (>%u levels deep)", max_nesting);
+ git_reference_free(ref);
+ return -1;
+ }
+
+ *ref_out = ref;
+ return 0;
+}
+
+int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
+{
+ int error = 0, i;
+ bool fallbackmode = true, foundvalid = false;
+ git_reference *ref;
+ git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
+
+ static const char* formatters[] = {
+ "%s",
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
+ NULL
+ };
+
+ if (*refname)
+ git_buf_puts(&name, refname);
+ else {
+ git_buf_puts(&name, GIT_HEAD_FILE);
+ fallbackmode = false;
+ }
+
+ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
+
+ git_buf_clear(&refnamebuf);
+
+ if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
+ goto cleanup;
+
+ if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
+ error = GIT_EINVALIDSPEC;
+ continue;
+ }
+ foundvalid = true;
+
+ error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
+
+ if (!error) {
+ *out = ref;
+ error = 0;
+ goto cleanup;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
+
+cleanup:
+ if (error && !foundvalid) {
+ /* never found a valid reference name */
+ giterr_set(GITERR_REFERENCE,
+ "Could not use '%s' as valid reference name", git_buf_cstr(&name));
+ }
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_REFERENCE, "no reference found for shorthand '%s'", refname);
+
+ git_buf_free(&name);
+ git_buf_free(&refnamebuf);
+ return error;
+}
+
+/**
+ * Getters
+ */
+git_ref_t git_reference_type(const git_reference *ref)
+{
+ assert(ref);
+ return ref->type;
+}
+
+const char *git_reference_name(const git_reference *ref)
+{
+ assert(ref);
+ return ref->name;
+}
+
+git_repository *git_reference_owner(const git_reference *ref)
+{
+ assert(ref);
+ return ref->db->repo;
+}
+
+const git_oid *git_reference_target(const git_reference *ref)
+{
+ assert(ref);
+
+ if (ref->type != GIT_REF_OID)
+ return NULL;
+
+ return &ref->target.oid;
+}
+
+const git_oid *git_reference_target_peel(const git_reference *ref)
+{
+ assert(ref);
+
+ if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel))
+ return NULL;
+
+ return &ref->peel;
+}
+
+const char *git_reference_symbolic_target(const git_reference *ref)
+{
+ assert(ref);
+
+ if (ref->type != GIT_REF_SYMBOLIC)
+ return NULL;
+
+ return ref->target.symbolic;
+}
+
+static int reference__create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *oid,
+ const char *symbolic,
+ int force,
+ const git_signature *signature,
+ const char *log_message,
+ const git_oid *old_id,
+ const char *old_target)
+{
+ git_refname_t normalized;
+ git_refdb *refdb;
+ git_reference *ref = NULL;
+ int error = 0;
+
+ assert(repo && name);
+ assert(symbolic || signature);
+
+ if (ref_out)
+ *ref_out = NULL;
+
+ error = reference_normalize_for_repo(normalized, repo, name);
+ if (error < 0)
+ return error;
+
+ error = git_repository_refdb__weakptr(&refdb, repo);
+ if (error < 0)
+ return error;
+
+ if (oid != NULL) {
+ assert(symbolic == NULL);
+
+ if (!git_object__is_valid(repo, oid, GIT_OBJ_ANY)) {
+ giterr_set(GITERR_REFERENCE,
+ "Target OID for the reference doesn't exist on the repository");
+ return -1;
+ }
+
+ ref = git_reference__alloc(normalized, oid, NULL);
+ } else {
+ git_refname_t normalized_target;
+
+ if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0)
+ return error;
+
+ ref = git_reference__alloc_symbolic(normalized, normalized_target);
+ }
+
+ GITERR_CHECK_ALLOC(ref);
+
+ if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
+ git_reference_free(ref);
+ return error;
+ }
+
+ if (ref_out == NULL)
+ git_reference_free(ref);
+ else
+ *ref_out = ref;
+
+ return 0;
+}
+
+int configured_ident(git_signature **out, const git_repository *repo)
+{
+ if (repo->ident_name && repo->ident_email)
+ return git_signature_now(out, repo->ident_name, repo->ident_email);
+
+ /* if not configured let us fall-through to the next method */
+ return -1;
+}
+
+int git_reference__log_signature(git_signature **out, git_repository *repo)
+{
+ int error;
+ git_signature *who;
+
+ if(((error = configured_ident(&who, repo)) < 0) &&
+ ((error = git_signature_default(&who, repo)) < 0) &&
+ ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
+ return error;
+
+ *out = who;
+ return 0;
+}
+
+int git_reference_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const git_oid *old_id,
+ const char *log_message)
+
+{
+ int error;
+ git_signature *who = NULL;
+
+ assert(id);
+
+ if ((error = git_reference__log_signature(&who, repo)) < 0)
+ return error;
+
+ error = reference__create(
+ ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL);
+
+ git_signature_free(who);
+ return error;
+}
+
+int git_reference_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const char *log_message)
+{
+ return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message);
+}
+
+int git_reference_symbolic_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const char *old_target,
+ const char *log_message)
+{
+ int error;
+ git_signature *who = NULL;
+
+ assert(target);
+
+ if ((error = git_reference__log_signature(&who, repo)) < 0)
+ return error;
+
+ error = reference__create(
+ ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target);
+
+ git_signature_free(who);
+ return error;
+}
+
+int git_reference_symbolic_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const char *log_message)
+{
+ return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message);
+}
+
+static int ensure_is_an_updatable_direct_reference(git_reference *ref)
+{
+ if (ref->type == GIT_REF_OID)
+ return 0;
+
+ giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
+ return -1;
+}
+
+int git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id,
+ const char *log_message)
+{
+ int error;
+ git_repository *repo;
+
+ assert(out && ref && id);
+
+ repo = ref->db->repo;
+
+ if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
+ return error;
+
+ return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message);
+}
+
+static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
+{
+ if (ref->type == GIT_REF_SYMBOLIC)
+ return 0;
+
+ giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference");
+ return -1;
+}
+
+int git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target,
+ const char *log_message)
+{
+ int error;
+
+ assert(out && ref && target);
+
+ if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
+ return error;
+
+ return git_reference_symbolic_create_matching(
+ out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message);
+}
+
+static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
+ const git_signature *signature, const char *message)
+{
+ git_refname_t normalized;
+ bool should_head_be_updated = false;
+ int error = 0;
+
+ assert(ref && new_name && signature);
+
+ if ((error = reference_normalize_for_repo(
+ normalized, git_reference_owner(ref), new_name)) < 0)
+ return error;
+
+
+ /* Check if we have to update HEAD. */
+ if ((error = git_branch_is_head(ref)) < 0)
+ return error;
+
+ should_head_be_updated = (error > 0);
+
+ if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
+ return error;
+
+ /* Update HEAD it was pointing to the reference being renamed */
+ if (should_head_be_updated &&
+ (error = git_repository_set_head(ref->db->repo, normalized)) < 0) {
+ giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
+ return error;
+ }
+
+ return 0;
+}
+
+
+int git_reference_rename(
+ git_reference **out,
+ git_reference *ref,
+ const char *new_name,
+ int force,
+ const char *log_message)
+{
+ git_signature *who;
+ int error;
+
+ if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0)
+ return error;
+
+ error = reference__rename(out, ref, new_name, force, who, log_message);
+ git_signature_free(who);
+
+ return error;
+}
+
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
+{
+ switch (git_reference_type(ref)) {
+ case GIT_REF_OID:
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
+
+ case GIT_REF_SYMBOLIC:
+ return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
+
+ default:
+ giterr_set(GITERR_REFERENCE, "Invalid reference");
+ return -1;
+ }
+}
+
+int git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ git_reference *ref;
+ int error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
+
+ while (!(error = git_reference_next(&ref, iter))) {
+ if ((error = callback(ref, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
+
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_foreach_glob(
+ git_repository *repo,
+ const char *glob,
+ git_reference_foreach_name_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
+ return error;
+
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
+{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, NULL);
+}
+
+int git_reference_iterator_glob_new(
+ git_reference_iterator **out, git_repository *repo, const char *glob)
+{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, glob);
+}
+
+int git_reference_next(git_reference **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next(out, iter);
+}
+
+int git_reference_next_name(const char **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next_name(out, iter);
+}
+
+void git_reference_iterator_free(git_reference_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ git_refdb_iterator_free(iter);
+}
+
+static int cb__reflist_add(const char *ref, void *data)
+{
+ char *name = git__strdup(ref);
+ GITERR_CHECK_ALLOC(name);
+ return git_vector_insert((git_vector *)data, name);
+}
+
+int git_reference_list(
+ git_strarray *array,
+ git_repository *repo)
+{
+ git_vector ref_list;
+
+ assert(array && repo);
+
+ array->strings = NULL;
+ array->count = 0;
+
+ if (git_vector_init(&ref_list, 8, NULL) < 0)
+ return -1;
+
+ if (git_reference_foreach_name(
+ repo, &cb__reflist_add, (void *)&ref_list) < 0) {
+ git_vector_free(&ref_list);
+ return -1;
+ }
+
+ array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list);
+
+ return 0;
+}
+
+static int is_valid_ref_char(char ch)
+{
+ if ((unsigned) ch <= ' ')
+ return 0;
+
+ switch (ch) {
+ case '~':
+ case '^':
+ case ':':
+ case '\\':
+ case '?':
+ case '[':
+ case '*':
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static int ensure_segment_validity(const char *name)
+{
+ const char *current = name;
+ char prev = '\0';
+ const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
+ int segment_len;
+
+ if (*current == '.')
+ return -1; /* Refname starts with "." */
+
+ for (current = name; ; current++) {
+ if (*current == '\0' || *current == '/')
+ break;
+
+ if (!is_valid_ref_char(*current))
+ return -1; /* Illegal character in refname */
+
+ if (prev == '.' && *current == '.')
+ return -1; /* Refname contains ".." */
+
+ if (prev == '@' && *current == '{')
+ return -1; /* Refname contains "@{" */
+
+ prev = *current;
+ }
+
+ segment_len = (int)(current - name);
+
+ /* A refname component can not end with ".lock" */
+ if (segment_len >= lock_len &&
+ !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
+ return -1;
+
+ return segment_len;
+}
+
+static bool is_all_caps_and_underscore(const char *name, size_t len)
+{
+ size_t i;
+ char c;
+
+ assert(name && len > 0);
+
+ for (i = 0; i < len; i++)
+ {
+ c = name[i];
+ if ((c < 'A' || c > 'Z') && c != '_')
+ return false;
+ }
+
+ if (*name == '_' || name[len - 1] == '_')
+ return false;
+
+ return true;
+}
+
+/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
+int git_reference__normalize_name(
+ git_buf *buf,
+ const char *name,
+ unsigned int flags)
+{
+ const char *current;
+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
+ unsigned int process_flags;
+ bool normalize = (buf != NULL);
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
+
+ assert(name);
+
+ process_flags = flags;
+ current = (char *)name;
+
+ if (*current == '/')
+ goto cleanup;
+
+ if (normalize)
+ git_buf_clear(buf);
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) {
+ size_t namelen = strlen(current);
+ if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
+ (error = git_path_iconv(&ic, ¤t, &namelen)) < 0)
+ goto cleanup;
+ error = GIT_EINVALIDSPEC;
+ }
+#endif
+
+ while (true) {
+ segment_len = ensure_segment_validity(current);
+ if (segment_len < 0) {
+ if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
+ current[0] == '*' &&
+ (current[1] == '\0' || current[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
+ segment_len = 1;
+ } else
+ goto cleanup;
+ }
+
+ if (segment_len > 0) {
+ if (normalize) {
+ size_t cur_len = git_buf_len(buf);
+
+ git_buf_joinpath(buf, git_buf_cstr(buf), current);
+ git_buf_truncate(buf,
+ cur_len + segment_len + (segments_count ? 1 : 0));
+
+ if (git_buf_oom(buf)) {
+ error = -1;
+ goto cleanup;
+ }
+ }
+
+ segments_count++;
+ }
+
+ /* No empty segment is allowed when not normalizing */
+ if (segment_len == 0 && !normalize)
+ goto cleanup;
+
+ if (current[segment_len] == '\0')
+ break;
+
+ current += segment_len + 1;
+ }
+
+ /* A refname can not be empty */
+ if (segment_len == 0 && segments_count == 0)
+ goto cleanup;
+
+ /* A refname can not end with "." */
+ if (current[segment_len - 1] == '.')
+ goto cleanup;
+
+ /* A refname can not end with "/" */
+ if (current[segment_len - 1] == '/')
+ goto cleanup;
+
+ if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
+ goto cleanup;
+
+ if ((segments_count == 1 ) &&
+ !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
+ ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
+ goto cleanup;
+
+ if ((segments_count > 1)
+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(
+ GITERR_REFERENCE,
+ "The given reference name '%s' is not valid", name);
+
+ if (error && normalize)
+ git_buf_free(buf);
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
+
+ return error;
+}
+
+int git_reference_normalize_name(
+ char *buffer_out,
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
+ goto cleanup;
+
+ if (git_buf_len(&buf) > buffer_size - 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "The provided buffer is too short to hold the normalization of '%s'", name);
+ error = GIT_EBUFS;
+ goto cleanup;
+ }
+
+ git_buf_copy_cstr(buffer_out, buffer_size, &buf);
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
+
+int git_reference_cmp(
+ const git_reference *ref1,
+ const git_reference *ref2)
+{
+ git_ref_t type1, type2;
+ assert(ref1 && ref2);
+
+ type1 = git_reference_type(ref1);
+ type2 = git_reference_type(ref2);
+
+ /* let's put symbolic refs before OIDs */
+ if (type1 != type2)
+ return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1;
+
+ if (type1 == GIT_REF_SYMBOLIC)
+ return strcmp(ref1->target.symbolic, ref2->target.symbolic);
+
+ return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
+}
+
+/**
+ * Get the end of a chain of references. If the final one is not
+ * found, we return the reference just before that.
+ */
+static int get_terminal(git_reference **out, git_repository *repo, const char *ref_name, int nesting)
+{
+ git_reference *ref;
+ int error = 0;
+
+ if (nesting > MAX_NESTING_LEVEL) {
+ giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
+ return GIT_ENOTFOUND;
+ }
+
+ /* set to NULL to let the caller know that they're at the end of the chain */
+ if ((error = git_reference_lookup(&ref, repo, ref_name)) < 0) {
+ *out = NULL;
+ return error;
+ }
+
+ if (git_reference_type(ref) == GIT_REF_OID) {
+ *out = ref;
+ error = 0;
+ } else {
+ error = get_terminal(out, repo, git_reference_symbolic_target(ref), nesting + 1);
+ if (error == GIT_ENOTFOUND && !*out)
+ *out = ref;
+ else
+ git_reference_free(ref);
+ }
+
+ return error;
+}
+
+/*
+ * Starting with the reference given by `ref_name`, follows symbolic
+ * references until a direct reference is found and updated the OID
+ * on that direct reference to `oid`.
+ */
+int git_reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ const git_signature *sig,
+ const char *log_message)
+{
+ git_reference *ref = NULL, *ref2 = NULL;
+ git_signature *who = NULL;
+ const git_signature *to_use;
+ int error = 0;
+
+ if (!sig && (error = git_reference__log_signature(&who, repo)) < 0)
+ return error;
+
+ to_use = sig ? sig : who;
+ error = get_terminal(&ref, repo, ref_name, 0);
+
+ /* found a dangling symref */
+ if (error == GIT_ENOTFOUND && ref) {
+ assert(git_reference_type(ref) == GIT_REF_SYMBOLIC);
+ giterr_clear();
+ error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use,
+ log_message, NULL, NULL);
+ } else if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use,
+ log_message, NULL, NULL);
+ } else if (error == 0) {
+ assert(git_reference_type(ref) == GIT_REF_OID);
+ error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use,
+ log_message, &ref->target.oid, NULL);
+ }
+
+ git_reference_free(ref2);
+ git_reference_free(ref);
+ git_signature_free(who);
+ return error;
+}
+
+int git_reference__update_for_commit(
+ git_repository *repo,
+ git_reference *ref,
+ const char *ref_name,
+ const git_oid *id,
+ const char *operation)
+{
+ git_reference *ref_new = NULL;
+ git_commit *commit = NULL;
+ git_buf reflog_msg = GIT_BUF_INIT;
+ const git_signature *who;
+ int error;
+
+ if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
+ (error = git_buf_printf(&reflog_msg, "%s%s: %s",
+ operation ? operation : "commit",
+ git_commit_parentcount(commit) == 0 ? " (initial)" : "",
+ git_commit_summary(commit))) < 0)
+ goto done;
+
+ who = git_commit_committer(commit);
+
+ if (ref) {
+ if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
+ return error;
+
+ error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who,
+ git_buf_cstr(&reflog_msg), &ref->target.oid, NULL);
+ }
+ else
+ error = git_reference__update_terminal(
+ repo, ref_name, id, who, git_buf_cstr(&reflog_msg));
+
+done:
+ git_reference_free(ref_new);
+ git_buf_free(&reflog_msg);
+ git_commit_free(commit);
+ return error;
+}
+
+int git_reference_has_log(git_repository *repo, const char *refname)
+{
+ int error;
+ git_refdb *refdb;
+
+ assert(repo && refname);
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ return git_refdb_has_log(refdb, refname);
+}
+
+int git_reference_ensure_log(git_repository *repo, const char *refname)
+{
+ int error;
+ git_refdb *refdb;
+
+ assert(repo && refname);
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ return git_refdb_ensure_log(refdb, refname);
+}
+
+int git_reference__is_branch(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_reference_is_branch(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_branch(ref->name);
+}
+
+int git_reference__is_remote(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
+}
+
+int git_reference_is_remote(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_remote(ref->name);
+}
+
+int git_reference__is_tag(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
+}
+
+int git_reference_is_tag(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_tag(ref->name);
+}
+
+int git_reference__is_note(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
+}
+
+int git_reference_is_note(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_note(ref->name);
+}
+
+static int peel_error(int error, git_reference *ref, const char* msg)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
+ return error;
+}
+
+int git_reference_peel(
+ git_object **peeled,
+ git_reference *ref,
+ git_otype target_type)
+{
+ git_reference *resolved = NULL;
+ git_object *target = NULL;
+ int error;
+
+ assert(ref);
+
+ if (ref->type == GIT_REF_OID) {
+ resolved = ref;
+ } else {
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+ }
+
+ if (!git_oid_iszero(&resolved->peel)) {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
+ } else {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
+ }
+
+ if (error < 0) {
+ peel_error(error, ref, "Cannot retrieve reference target");
+ goto cleanup;
+ }
+
+ if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
+ error = git_object_dup(peeled, target);
+ else
+ error = git_object_peel(peeled, target, target_type);
+
+cleanup:
+ git_object_free(target);
+
+ if (resolved != ref)
+ git_reference_free(resolved);
+
+ return error;
+}
+
+int git_reference__is_valid_name(const char *refname, unsigned int flags)
+{
+ if (git_reference__normalize_name(NULL, refname, flags) < 0) {
+ giterr_clear();
+ return false;
+ }
+
+ return true;
+}
+
+int git_reference_is_valid_name(const char *refname)
+{
+ return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL);
+}
+
+const char *git_reference__shorthand(const char *name)
+{
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ return name + strlen(GIT_REFS_HEADS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return name + strlen(GIT_REFS_TAGS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
+ return name + strlen(GIT_REFS_REMOTES_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_DIR))
+ return name + strlen(GIT_REFS_DIR);
+
+ /* No shorthands are avaiable, so just return the name */
+ return name;
+}
+
+const char *git_reference_shorthand(const git_reference *ref)
+{
+ return git_reference__shorthand(ref->name);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refs_h__
+#define INCLUDE_refs_h__
+
+#include "common.h"
+#include "git2/oid.h"
+#include "git2/refs.h"
+#include "git2/refdb.h"
+#include "strmap.h"
+#include "buffer.h"
+#include "oid.h"
+
+#define GIT_REFS_DIR "refs/"
+#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
+#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/"
+#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/"
+#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/"
+#define GIT_REFS_DIR_MODE 0777
+#define GIT_REFS_FILE_MODE 0666
+
+#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF"
+
+#define GIT_SYMREF "ref: "
+#define GIT_PACKEDREFS_FILE "packed-refs"
+#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled "
+#define GIT_PACKEDREFS_FILE_MODE 0666
+
+#define GIT_HEAD_FILE "HEAD"
+#define GIT_ORIG_HEAD_FILE "ORIG_HEAD"
+#define GIT_FETCH_HEAD_FILE "FETCH_HEAD"
+#define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
+#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"
+#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD"
+#define GIT_BISECT_LOG_FILE "BISECT_LOG"
+#define GIT_REBASE_MERGE_DIR "rebase-merge/"
+#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive"
+#define GIT_REBASE_APPLY_DIR "rebase-apply/"
+#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing"
+#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying"
+#define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master"
+
+#define GIT_SEQUENCER_DIR "sequencer/"
+#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head"
+#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options"
+#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo"
+
+#define GIT_STASH_FILE "stash"
+#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE
+
+#define GIT_REF_FORMAT__PRECOMPOSE_UNICODE (1u << 16)
+
+#define GIT_REFNAME_MAX 1024
+
+typedef char git_refname_t[GIT_REFNAME_MAX];
+
+struct git_reference {
+ git_refdb *db;
+ git_ref_t type;
+
+ union {
+ git_oid oid;
+ char *symbolic;
+ } target;
+
+ git_oid peel;
+ char name[GIT_FLEX_ARRAY];
+};
+
+git_reference *git_reference__set_name(git_reference *ref, const char *name);
+
+int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags);
+int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *sig, const char *log_message);
+int git_reference__is_valid_name(const char *refname, unsigned int flags);
+int git_reference__is_branch(const char *ref_name);
+int git_reference__is_remote(const char *ref_name);
+int git_reference__is_tag(const char *ref_name);
+const char *git_reference__shorthand(const char *name);
+
+/**
+ * Lookup a reference by name and try to resolve to an OID.
+ *
+ * You can control how many dereferences this will attempt to resolve the
+ * reference with the `max_deref` parameter, or pass -1 to use a sane
+ * default. If you pass 0 for `max_deref`, this will not attempt to resolve
+ * the reference. For any value of `max_deref` other than 0, not
+ * successfully resolving the reference will be reported as an error.
+
+ * The generated reference must be freed by the user.
+ *
+ * @param reference_out Pointer to the looked-up reference
+ * @param repo The repository to look up the reference
+ * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...)
+ * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value
+ * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref
+ */
+int git_reference_lookup_resolved(
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *name,
+ int max_deref);
+
+int git_reference__log_signature(git_signature **out, git_repository *repo);
+
+/** Update a reference after a commit. */
+int git_reference__update_for_commit(
+ git_repository *repo,
+ git_reference *ref,
+ const char *ref_name,
+ const git_oid *id,
+ const char *operation);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/errors.h"
+
+#include "common.h"
+#include "refspec.h"
+#include "util.h"
+#include "posix.h"
+#include "refs.h"
+#include "vector.h"
+
+int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
+{
+ // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636
+
+ size_t llen;
+ int is_glob = 0;
+ const char *lhs, *rhs;
+ int flags;
+
+ assert(refspec && input);
+
+ memset(refspec, 0x0, sizeof(git_refspec));
+ refspec->push = !is_fetch;
+
+ lhs = input;
+ if (*lhs == '+') {
+ refspec->force = 1;
+ lhs++;
+ }
+
+ rhs = strrchr(lhs, ':');
+
+ /*
+ * Before going on, special case ":" (or "+:") as a refspec
+ * for matching refs.
+ */
+ if (!is_fetch && rhs == lhs && rhs[1] == '\0') {
+ refspec->matching = 1;
+ refspec->string = git__strdup(input);
+ GITERR_CHECK_ALLOC(refspec->string);
+ refspec->src = git__strdup("");
+ GITERR_CHECK_ALLOC(refspec->src);
+ refspec->dst = git__strdup("");
+ GITERR_CHECK_ALLOC(refspec->dst);
+ return 0;
+ }
+
+ if (rhs) {
+ size_t rlen = strlen(++rhs);
+ if (rlen || !is_fetch) {
+ is_glob = (1 <= rlen && strchr(rhs, '*'));
+ refspec->dst = git__strndup(rhs, rlen);
+ }
+ }
+
+ llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
+ if (1 <= llen && memchr(lhs, '*', llen)) {
+ if ((rhs && !is_glob) || (!rhs && is_fetch))
+ goto invalid;
+ is_glob = 1;
+ } else if (rhs && is_glob)
+ goto invalid;
+
+ refspec->pattern = is_glob;
+ refspec->src = git__strndup(lhs, llen);
+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL | GIT_REF_FORMAT_REFSPEC_SHORTHAND
+ | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
+
+ if (is_fetch) {
+ /*
+ * LHS
+ * - empty is allowed; it means HEAD.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ /*
+ * RHS
+ * - missing is ok, and is same as empty.
+ * - empty is ok; it means not to store.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst)
+ ; /* ok */
+ else if (!*refspec->dst)
+ ; /* ok */
+ else if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ } else {
+ /*
+ * LHS
+ * - empty is allowed; it means delete.
+ * - when wildcarded, it must be a valid looking ref.
+ * - otherwise, it must be an extended SHA-1, but
+ * there is no existing way to validate this.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (is_glob) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ }
+ else {
+ ; /* anything goes, for now */
+ }
+ /*
+ * RHS
+ * - missing is allowed, but LHS then must be a
+ * valid looking ref.
+ * - empty is not allowed.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ } else if (!*refspec->dst) {
+ goto invalid;
+ } else {
+ if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ }
+
+ /* if the RHS is empty, then it's a copy of the LHS */
+ if (!refspec->dst) {
+ refspec->dst = git__strdup(refspec->src);
+ GITERR_CHECK_ALLOC(refspec->dst);
+ }
+ }
+
+ refspec->string = git__strdup(input);
+ GITERR_CHECK_ALLOC(refspec->string);
+
+ return 0;
+
+ invalid:
+ giterr_set(
+ GITERR_INVALID,
+ "'%s' is not a valid refspec.", input);
+ git_refspec__free(refspec);
+ return -1;
+}
+
+void git_refspec__free(git_refspec *refspec)
+{
+ if (refspec == NULL)
+ return;
+
+ git__free(refspec->src);
+ git__free(refspec->dst);
+ git__free(refspec->string);
+
+ memset(refspec, 0x0, sizeof(git_refspec));
+}
+
+const char *git_refspec_src(const git_refspec *refspec)
+{
+ return refspec == NULL ? NULL : refspec->src;
+}
+
+const char *git_refspec_dst(const git_refspec *refspec)
+{
+ return refspec == NULL ? NULL : refspec->dst;
+}
+
+const char *git_refspec_string(const git_refspec *refspec)
+{
+ return refspec == NULL ? NULL : refspec->string;
+}
+
+int git_refspec_force(const git_refspec *refspec)
+{
+ assert(refspec);
+
+ return refspec->force;
+}
+
+int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
+{
+ if (refspec == NULL || refspec->src == NULL)
+ return false;
+
+ return (p_fnmatch(refspec->src, refname, 0) == 0);
+}
+
+int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
+{
+ if (refspec == NULL || refspec->dst == NULL)
+ return false;
+
+ return (p_fnmatch(refspec->dst, refname, 0) == 0);
+}
+
+static int refspec_transform(
+ git_buf *out, const char *from, const char *to, const char *name)
+{
+ const char *from_star, *to_star;
+ const char *name_slash, *from_slash;
+ size_t replacement_len, star_offset;
+
+ git_buf_sanitize(out);
+ git_buf_clear(out);
+
+ /*
+ * There are two parts to each side of a refspec, the bit
+ * before the star and the bit after it. The star can be in
+ * the middle of the pattern, so we need to look at each bit
+ * individually.
+ */
+ from_star = strchr(from, '*');
+ to_star = strchr(to, '*');
+
+ assert(from_star && to_star);
+
+ /* star offset, both in 'from' and in 'name' */
+ star_offset = from_star - from;
+
+ /* the first half is copied over */
+ git_buf_put(out, to, to_star - to);
+
+ /* then we copy over the replacement, from the star's offset to the next slash in 'name' */
+ name_slash = strchr(name + star_offset, '/');
+ if (!name_slash)
+ name_slash = strrchr(name, '\0');
+
+ /* if there is no slash after the star in 'from', we want to copy everything over */
+ from_slash = strchr(from + star_offset, '/');
+ if (!from_slash)
+ name_slash = strrchr(name, '\0');
+
+ replacement_len = (name_slash - name) - star_offset;
+ git_buf_put(out, name + star_offset, replacement_len);
+
+ return git_buf_puts(out, to_star + 1);
+}
+
+int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name)
+{
+ assert(out && spec && name);
+ git_buf_sanitize(out);
+
+ if (!git_refspec_src_matches(spec, name)) {
+ giterr_set(GITERR_INVALID, "ref '%s' doesn't match the source", name);
+ return -1;
+ }
+
+ if (!spec->pattern)
+ return git_buf_puts(out, spec->dst);
+
+ return refspec_transform(out, spec->src, spec->dst, name);
+}
+
+int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name)
+{
+ assert(out && spec && name);
+ git_buf_sanitize(out);
+
+ if (!git_refspec_dst_matches(spec, name)) {
+ giterr_set(GITERR_INVALID, "ref '%s' doesn't match the destination", name);
+ return -1;
+ }
+
+ if (!spec->pattern)
+ return git_buf_puts(out, spec->src);
+
+ return refspec_transform(out, spec->dst, spec->src, name);
+}
+
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
+{
+ if (refspec->force)
+ git_buf_putc(out, '+');
+
+ git_buf_printf(out, "%s:%s",
+ refspec->src != NULL ? refspec->src : "",
+ refspec->dst != NULL ? refspec->dst : "");
+
+ return git_buf_oom(out) == false;
+}
+
+int git_refspec_is_wildcard(const git_refspec *spec)
+{
+ assert(spec && spec->src);
+
+ return (spec->src[strlen(spec->src) - 1] == '*');
+}
+
+git_direction git_refspec_direction(const git_refspec *spec)
+{
+ assert(spec);
+
+ return spec->push;
+}
+
+int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t j, pos;
+ git_remote_head key;
+
+ const char* formatters[] = {
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ NULL
+ };
+
+ git_refspec *cur = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(cur);
+
+ cur->force = spec->force;
+ cur->push = spec->push;
+ cur->pattern = spec->pattern;
+ cur->matching = spec->matching;
+ cur->string = git__strdup(spec->string);
+
+ /* shorthand on the lhs */
+ if (git__prefixcmp(spec->src, GIT_REFS_DIR)) {
+ for (j = 0; formatters[j]; j++) {
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, formatters[j], spec->src);
+ GITERR_CHECK_ALLOC_BUF(&buf);
+
+ key.name = (char *) git_buf_cstr(&buf);
+ if (!git_vector_search(&pos, refs, &key)) {
+ /* we found something to match the shorthand, set src to that */
+ cur->src = git_buf_detach(&buf);
+ }
+ }
+ }
+
+ /* No shorthands found, copy over the name */
+ if (cur->src == NULL && spec->src != NULL) {
+ cur->src = git__strdup(spec->src);
+ GITERR_CHECK_ALLOC(cur->src);
+ }
+
+ if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) {
+ /* if it starts with "remotes" then we just prepend "refs/" */
+ if (!git__prefixcmp(spec->dst, "remotes/")) {
+ git_buf_puts(&buf, GIT_REFS_DIR);
+ } else {
+ git_buf_puts(&buf, GIT_REFS_HEADS_DIR);
+ }
+
+ git_buf_puts(&buf, spec->dst);
+ GITERR_CHECK_ALLOC_BUF(&buf);
+
+ cur->dst = git_buf_detach(&buf);
+ }
+
+ git_buf_free(&buf);
+
+ if (cur->dst == NULL && spec->dst != NULL) {
+ cur->dst = git__strdup(spec->dst);
+ GITERR_CHECK_ALLOC(cur->dst);
+ }
+
+ return git_vector_insert(out, cur);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refspec_h__
+#define INCLUDE_refspec_h__
+
+#include "git2/refspec.h"
+#include "buffer.h"
+#include "vector.h"
+
+struct git_refspec {
+ char *string;
+ char *src;
+ char *dst;
+ unsigned int force :1,
+ push : 1,
+ pattern :1,
+ matching :1;
+};
+
+#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"
+
+int git_refspec__parse(
+ struct git_refspec *refspec,
+ const char *str,
+ bool is_fetch);
+
+void git_refspec__free(git_refspec *refspec);
+
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec);
+
+/**
+ * Determines if a refspec is a wildcard refspec.
+ *
+ * @param spec the refspec
+ * @return 1 if the refspec is a wildcard, 0 otherwise
+ */
+int git_refspec_is_wildcard(const git_refspec *spec);
+
+/**
+ * DWIM `spec` with `refs` existing on the remote, append the dwim'ed
+ * result in `out`.
+ */
+int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/config.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/net.h"
+
+#include "common.h"
+#include "config.h"
+#include "repository.h"
+#include "remote.h"
+#include "fetch.h"
+#include "refs.h"
+#include "refspec.h"
+#include "fetchhead.h"
+#include "push.h"
+
+#define CONFIG_URL_FMT "remote.%s.url"
+#define CONFIG_PUSHURL_FMT "remote.%s.pushurl"
+#define CONFIG_FETCH_FMT "remote.%s.fetch"
+#define CONFIG_PUSH_FMT "remote.%s.push"
+#define CONFIG_TAGOPT_FMT "remote.%s.tagopt"
+
+static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs);
+static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name);
+char *apply_insteadof(git_config *config, const char *url, int direction);
+
+static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch)
+{
+ git_refspec *spec;
+
+ spec = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(spec);
+
+ if (git_refspec__parse(spec, string, is_fetch) < 0) {
+ git__free(spec);
+ return -1;
+ }
+
+ spec->push = !is_fetch;
+ if (git_vector_insert(vector, spec) < 0) {
+ git_refspec__free(spec);
+ git__free(spec);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_refspec(git_remote *remote, const char *string, bool is_fetch)
+{
+ return add_refspec_to(&remote->refspecs, string, is_fetch);
+}
+
+static int download_tags_value(git_remote *remote, git_config *cfg)
+{
+ git_config_entry *ce;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
+ return -1;
+
+ error = git_config__lookup_entry(&ce, cfg, git_buf_cstr(&buf), false);
+ git_buf_free(&buf);
+
+ if (!error && ce && ce->value) {
+ if (!strcmp(ce->value, "--no-tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+ else if (!strcmp(ce->value, "--tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
+ }
+
+ git_config_entry_free(ce);
+ return error;
+}
+
+static int ensure_remote_name_is_valid(const char *name)
+{
+ int error = 0;
+
+ if (!git_remote_is_valid_name(name)) {
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name ? name : "(null)");
+ error = GIT_EINVALIDSPEC;
+ }
+
+ return error;
+}
+
+static int write_add_refspec(git_repository *repo, const char *name, const char *refspec, bool fetch)
+{
+ git_config *cfg;
+ git_buf var = GIT_BUF_INIT;
+ git_refspec spec;
+ const char *fmt;
+ int error;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ fmt = fetch ? CONFIG_FETCH_FMT : CONFIG_PUSH_FMT;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = git_refspec__parse(&spec, refspec, fetch)) < 0) {
+ if (giterr_last()->klass != GITERR_NOMEMORY)
+ error = GIT_EINVALIDSPEC;
+
+ return error;
+ }
+
+ git_refspec__free(&spec);
+
+ if ((error = git_buf_printf(&var, fmt, name)) < 0)
+ return error;
+
+ /*
+ * "$^" is a unmatcheable regexp: it will not match anything at all, so
+ * all values will be considered new and we will not replace any
+ * present value.
+ */
+ if ((error = git_config_set_multivar(cfg, var.ptr, "$^", refspec)) < 0) {
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&var);
+ return 0;
+}
+
+#if 0
+/* We could export this as a helper */
+static int get_check_cert(int *out, git_repository *repo)
+{
+ git_config *cfg;
+ const char *val;
+ int error = 0;
+
+ assert(out && repo);
+
+ /* By default, we *DO* want to verify the certificate. */
+ *out = 1;
+
+ /* Go through the possible sources for SSL verification settings, from
+ * most specific to least specific. */
+
+ /* GIT_SSL_NO_VERIFY environment variable */
+ if ((val = p_getenv("GIT_SSL_NO_VERIFY")) != NULL)
+ return git_config_parse_bool(out, val);
+
+ /* http.sslVerify config setting */
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ *out = git_config__get_bool_force(cfg, "http.sslverify", 1);
+ return 0;
+}
+#endif
+
+static int canonicalize_url(git_buf *out, const char *in)
+{
+ if (in == NULL || strlen(in) == 0) {
+ giterr_set(GITERR_INVALID, "cannot set empty URL");
+ return GIT_EINVALIDSPEC;
+ }
+
+#ifdef GIT_WIN32
+ /* Given a UNC path like \\server\path, we need to convert this
+ * to //server/path for compatibility with core git.
+ */
+ if (in[0] == '\\' && in[1] == '\\' &&
+ (git__isalpha(in[2]) || git__isdigit(in[2]))) {
+ const char *c;
+ for (c = in; *c; c++)
+ git_buf_putc(out, *c == '\\' ? '/' : *c);
+
+ return git_buf_oom(out) ? -1 : 0;
+ }
+#endif
+
+ return git_buf_puts(out, in);
+}
+
+static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
+{
+ git_remote *remote;
+ git_config *config = NULL;
+ git_buf canonical_url = GIT_BUF_INIT;
+ git_buf var = GIT_BUF_INIT;
+ int error = -1;
+
+ /* name is optional */
+ assert(out && repo && url);
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ remote = git__calloc(1, sizeof(git_remote));
+ GITERR_CHECK_ALLOC(remote);
+
+ remote->repo = repo;
+
+ if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
+ (error = canonicalize_url(&canonical_url, url)) < 0)
+ goto on_error;
+
+ remote->url = apply_insteadof(repo->_config, canonical_url.ptr, GIT_DIRECTION_FETCH);
+
+ if (name != NULL) {
+ remote->name = git__strdup(name);
+ GITERR_CHECK_ALLOC(remote->name);
+
+ if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0)
+ goto on_error;
+
+ if ((error = git_config_set_string(config, var.ptr, canonical_url.ptr)) < 0)
+ goto on_error;
+ }
+
+ if (fetch != NULL) {
+ if ((error = add_refspec(remote, fetch, true)) < 0)
+ goto on_error;
+
+ /* only write for non-anonymous remotes */
+ if (name && (error = write_add_refspec(repo, name, fetch, true)) < 0)
+ goto on_error;
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ goto on_error;
+
+ if ((error = lookup_remote_prune_config(remote, config, name)) < 0)
+ goto on_error;
+
+ /* Move the data over to where the matching functions can find them */
+ if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0)
+ goto on_error;
+ }
+
+ /* A remote without a name doesn't download tags */
+ if (!name)
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+ else
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
+
+
+ git_buf_free(&var);
+
+ *out = remote;
+ error = 0;
+
+on_error:
+ if (error)
+ git_remote_free(remote);
+
+ git_config_free(config);
+ git_buf_free(&canonical_url);
+ git_buf_free(&var);
+ return error;
+}
+
+static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
+{
+ int error;
+ git_remote *remote;
+
+ error = git_remote_lookup(&remote, repo, name);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ git_remote_free(remote);
+
+ giterr_set(
+ GITERR_CONFIG,
+ "Remote '%s' already exists.", name);
+
+ return GIT_EEXISTS;
+}
+
+
+int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return -1;
+
+ error = git_remote_create_with_fetchspec(out, repo, name, url, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+
+ return error;
+}
+
+int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
+{
+ git_remote *remote = NULL;
+ int error;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
+ return error;
+
+ if (create_internal(&remote, repo, name, url, fetch) < 0)
+ goto on_error;
+
+ *out = remote;
+
+ return 0;
+
+on_error:
+ git_remote_free(remote);
+ return -1;
+}
+
+int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url)
+{
+ return create_internal(out, repo, NULL, url, NULL);
+}
+
+int git_remote_dup(git_remote **dest, git_remote *source)
+{
+ size_t i;
+ int error = 0;
+ git_refspec *spec;
+ git_remote *remote = git__calloc(1, sizeof(git_remote));
+ GITERR_CHECK_ALLOC(remote);
+
+ if (source->name != NULL) {
+ remote->name = git__strdup(source->name);
+ GITERR_CHECK_ALLOC(remote->name);
+ }
+
+ if (source->url != NULL) {
+ remote->url = git__strdup(source->url);
+ GITERR_CHECK_ALLOC(remote->url);
+ }
+
+ if (source->pushurl != NULL) {
+ remote->pushurl = git__strdup(source->pushurl);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ }
+
+ remote->repo = source->repo;
+ remote->download_tags = source->download_tags;
+ remote->prune_refs = source->prune_refs;
+
+ if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
+ git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
+ git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ git_vector_foreach(&source->refspecs, i, spec) {
+ if ((error = add_refspec(remote, spec->string, !spec->push)) < 0)
+ goto cleanup;
+ }
+
+ *dest = remote;
+
+cleanup:
+
+ if (error < 0)
+ git__free(remote);
+
+ return error;
+}
+
+struct refspec_cb_data {
+ git_remote *remote;
+ int fetch;
+};
+
+static int refspec_cb(const git_config_entry *entry, void *payload)
+{
+ struct refspec_cb_data *data = (struct refspec_cb_data *)payload;
+ return add_refspec(data->remote, entry->value, data->fetch);
+}
+
+static int get_optional_config(
+ bool *found, git_config *config, git_buf *buf,
+ git_config_foreach_cb cb, void *payload)
+{
+ int error = 0;
+ const char *key = git_buf_cstr(buf);
+
+ if (git_buf_oom(buf))
+ return -1;
+
+ if (cb != NULL)
+ error = git_config_get_multivar_foreach(config, key, NULL, cb, payload);
+ else
+ error = git_config_get_string(payload, config, key);
+
+ if (found)
+ *found = !error;
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+}
+
+int git_remote_lookup(git_remote **out, git_repository *repo, const char *name)
+{
+ git_remote *remote;
+ git_buf buf = GIT_BUF_INIT;
+ const char *val;
+ int error = 0;
+ git_config *config;
+ struct refspec_cb_data data = { NULL };
+ bool optional_setting_found = false, found;
+
+ assert(out && repo && name);
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
+
+ remote = git__calloc(1, sizeof(git_remote));
+ GITERR_CHECK_ALLOC(remote);
+
+ remote->name = git__strdup(name);
+ GITERR_CHECK_ALLOC(remote->name);
+
+ if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
+ git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
+ git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 ||
+ git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_buf_printf(&buf, "remote.%s.url", name)) < 0)
+ goto cleanup;
+
+ if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0)
+ goto cleanup;
+
+ optional_setting_found |= found;
+
+ remote->repo = repo;
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
+
+ if (found && strlen(val) > 0) {
+ remote->url = apply_insteadof(config, val, GIT_DIRECTION_FETCH);
+ GITERR_CHECK_ALLOC(remote->url);
+ }
+
+ val = NULL;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.pushurl", name);
+
+ if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0)
+ goto cleanup;
+
+ optional_setting_found |= found;
+
+ if (!optional_setting_found) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_CONFIG, "Remote '%s' does not exist.", name);
+ goto cleanup;
+ }
+
+ if (found && strlen(val) > 0) {
+ remote->pushurl = apply_insteadof(config, val, GIT_DIRECTION_PUSH);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ }
+
+ data.remote = remote;
+ data.fetch = true;
+
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.fetch", name);
+
+ if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0)
+ goto cleanup;
+
+ data.fetch = false;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.push", name);
+
+ if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0)
+ goto cleanup;
+
+ if (download_tags_value(remote, config) < 0)
+ goto cleanup;
+
+ if ((error = lookup_remote_prune_config(remote, config, name)) < 0)
+ goto cleanup;
+
+ /* Move the data over to where the matching functions can find them */
+ if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0)
+ goto cleanup;
+
+ *out = remote;
+
+cleanup:
+ git_config_free(config);
+ git_buf_free(&buf);
+
+ if (error < 0)
+ git_remote_free(remote);
+
+ return error;
+}
+
+static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error = 0;
+
+ git_buf_printf(&buf, "remote.%s.prune", name);
+
+ if ((error = git_config_get_bool(&remote->prune_refs, config, git_buf_cstr(&buf))) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+
+ if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+ }
+ }
+ }
+
+ git_buf_free(&buf);
+ return error;
+}
+
+const char *git_remote_name(const git_remote *remote)
+{
+ assert(remote);
+ return remote->name;
+}
+
+git_repository *git_remote_owner(const git_remote *remote)
+{
+ assert(remote);
+ return remote->repo;
+}
+
+const char *git_remote_url(const git_remote *remote)
+{
+ assert(remote);
+ return remote->url;
+}
+
+static int set_url(git_repository *repo, const char *remote, const char *pattern, const char *url)
+{
+ git_config *cfg;
+ git_buf buf = GIT_BUF_INIT, canonical_url = GIT_BUF_INIT;
+ int error;
+
+ assert(repo && remote);
+
+ if ((error = ensure_remote_name_is_valid(remote)) < 0)
+ return error;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if ((error = git_buf_printf(&buf, pattern, remote)) < 0)
+ return error;
+
+ if (url) {
+ if ((error = canonicalize_url(&canonical_url, url)) < 0)
+ goto cleanup;
+
+ error = git_config_set_string(cfg, buf.ptr, url);
+ } else {
+ error = git_config_delete_entry(cfg, buf.ptr);
+ }
+
+cleanup:
+ git_buf_free(&canonical_url);
+ git_buf_free(&buf);
+
+ return error;
+}
+
+int git_remote_set_url(git_repository *repo, const char *remote, const char *url)
+{
+ return set_url(repo, remote, CONFIG_URL_FMT, url);
+}
+
+const char *git_remote_pushurl(const git_remote *remote)
+{
+ assert(remote);
+ return remote->pushurl;
+}
+
+int git_remote_set_pushurl(git_repository *repo, const char *remote, const char* url)
+{
+ return set_url(repo, remote, CONFIG_PUSHURL_FMT, url);
+}
+
+const char* git_remote__urlfordirection(git_remote *remote, int direction)
+{
+ assert(remote);
+
+ assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH);
+
+ if (direction == GIT_DIRECTION_FETCH) {
+ return remote->url;
+ }
+
+ if (direction == GIT_DIRECTION_PUSH) {
+ return remote->pushurl ? remote->pushurl : remote->url;
+ }
+
+ return NULL;
+}
+
+int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs)
+{
+ if (!t->set_callbacks || !cbs)
+ return 0;
+
+ return t->set_callbacks(t, cbs->sideband_progress, NULL,
+ cbs->certificate_check, cbs->payload);
+}
+
+static int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers)
+{
+ if (!t->set_custom_headers)
+ return 0;
+
+ return t->set_custom_headers(t, custom_headers);
+}
+
+int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy, const git_strarray *custom_headers)
+{
+ git_transport *t;
+ const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
+ int error;
+ void *payload = NULL;
+ git_cred_acquire_cb credentials = NULL;
+ git_transport_cb transport = NULL;
+
+ assert(remote);
+
+ if (callbacks) {
+ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+ credentials = callbacks->credentials;
+ transport = callbacks->transport;
+ payload = callbacks->payload;
+ }
+
+ if (proxy)
+ GITERR_CHECK_VERSION(proxy, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options");
+
+ t = remote->transport;
+
+ url = git_remote__urlfordirection(remote, direction);
+ if (url == NULL) {
+ giterr_set(GITERR_INVALID,
+ "Malformed remote '%s' - missing URL", remote->name);
+ return -1;
+ }
+
+ /* If we don't have a transport object yet, and the caller specified a
+ * custom transport factory, use that */
+ if (!t && transport &&
+ (error = transport(&t, remote, payload)) < 0)
+ return error;
+
+ /* If we still don't have a transport, then use the global
+ * transport registrations which map URI schemes to transport factories */
+ if (!t && (error = git_transport_new(&t, remote, url)) < 0)
+ return error;
+
+ if ((error = set_transport_custom_headers(t, custom_headers)) != 0)
+ goto on_error;
+
+ if ((error = set_transport_callbacks(t, callbacks)) < 0 ||
+ (error = t->connect(t, url, credentials, payload, proxy, direction, flags)) != 0)
+ goto on_error;
+
+ remote->transport = t;
+
+ return 0;
+
+on_error:
+ t->free(t);
+
+ if (t == remote->transport)
+ remote->transport = NULL;
+
+ return error;
+}
+
+int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote)
+{
+ assert(remote);
+
+ if (!remote->transport) {
+ giterr_set(GITERR_NET, "this remote has never connected");
+ return -1;
+ }
+
+ return remote->transport->ls(out, size, remote->transport);
+}
+
+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url)
+{
+ git_config *cfg;
+ git_config_entry *ce = NULL;
+ git_buf val = GIT_BUF_INIT;
+ int error;
+
+ assert(remote);
+
+ if (!proxy_url || !remote->repo)
+ return -1;
+
+ *proxy_url = NULL;
+
+ if ((error = git_repository_config__weakptr(&cfg, remote->repo)) < 0)
+ return error;
+
+ /* Go through the possible sources for proxy configuration, from most specific
+ * to least specific. */
+
+ /* remote.<name>.proxy config setting */
+ if (remote->name && remote->name[0]) {
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_buf_printf(&buf, "remote.%s.proxy", remote->name)) < 0)
+ return error;
+
+ error = git_config__lookup_entry(&ce, cfg, git_buf_cstr(&buf), false);
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ if (ce && ce->value) {
+ *proxy_url = git__strdup(ce->value);
+ goto found;
+ }
+ }
+
+ /* http.proxy config setting */
+ if ((error = git_config__lookup_entry(&ce, cfg, "http.proxy", false)) < 0)
+ return error;
+
+ if (ce && ce->value) {
+ *proxy_url = git__strdup(ce->value);
+ goto found;
+ }
+
+ /* HTTP_PROXY / HTTPS_PROXY environment variables */
+ error = git__getenv(&val, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY");
+
+ if (error < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+ }
+
+ *proxy_url = git_buf_detach(&val);
+
+found:
+ GITERR_CHECK_ALLOC(*proxy_url);
+ git_config_entry_free(ce);
+
+ return 0;
+}
+
+/* DWIM `refspecs` based on `refs` and append the output to `out` */
+static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs)
+{
+ size_t i;
+ git_refspec *spec;
+
+ git_vector_foreach(refspecs, i, spec) {
+ if (git_refspec__dwim_one(out, spec, refs) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_refspecs(git_vector *vec)
+{
+ size_t i;
+ git_refspec *spec;
+
+ git_vector_foreach(vec, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+
+ git_vector_clear(vec);
+}
+
+static int remote_head_cmp(const void *_a, const void *_b)
+{
+ const git_remote_head *a = (git_remote_head *) _a;
+ const git_remote_head *b = (git_remote_head *) _b;
+
+ return git__strcmp_cb(a->name, b->name);
+}
+
+static int ls_to_vector(git_vector *out, git_remote *remote)
+{
+ git_remote_head **heads;
+ size_t heads_len, i;
+
+ if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0)
+ return -1;
+
+ if (git_vector_init(out, heads_len, remote_head_cmp) < 0)
+ return -1;
+
+ for (i = 0; i < heads_len; i++) {
+ if (git_vector_insert(out, heads[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_fetch_options *opts)
+{
+ int error = -1;
+ size_t i;
+ git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT;
+ const git_remote_callbacks *cbs = NULL;
+ const git_strarray *custom_headers = NULL;
+ const git_proxy_options *proxy = NULL;
+
+ assert(remote);
+
+ if (opts) {
+ GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+ cbs = &opts->callbacks;
+ custom_headers = &opts->custom_headers;
+ GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options");
+ proxy = &opts->proxy_opts;
+ }
+
+ if (!git_remote_connected(remote) &&
+ (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, proxy, custom_headers)) < 0)
+ goto on_error;
+
+ if (ls_to_vector(&refs, remote) < 0)
+ return -1;
+
+ if ((git_vector_init(&specs, 0, NULL)) < 0)
+ goto on_error;
+
+ remote->passed_refspecs = 0;
+ if (!refspecs || !refspecs->count) {
+ to_active = &remote->refspecs;
+ } else {
+ for (i = 0; i < refspecs->count; i++) {
+ if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0)
+ goto on_error;
+ }
+
+ to_active = &specs;
+ remote->passed_refspecs = 1;
+ }
+
+ free_refspecs(&remote->passive_refspecs);
+ if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0)
+ goto on_error;
+
+ free_refspecs(&remote->active_refspecs);
+ error = dwim_refspecs(&remote->active_refspecs, to_active, &refs);
+
+ git_vector_free(&refs);
+ free_refspecs(&specs);
+ git_vector_free(&specs);
+
+ if (error < 0)
+ return error;
+
+ if (remote->push) {
+ git_push_free(remote->push);
+ remote->push = NULL;
+ }
+
+ if ((error = git_fetch_negotiate(remote, opts)) < 0)
+ return error;
+
+ return git_fetch_download_pack(remote, cbs);
+
+on_error:
+ git_vector_free(&refs);
+ free_refspecs(&specs);
+ git_vector_free(&specs);
+ return error;
+}
+
+int git_remote_fetch(
+ git_remote *remote,
+ const git_strarray *refspecs,
+ const git_fetch_options *opts,
+ const char *reflog_message)
+{
+ int error, update_fetchhead = 1;
+ git_remote_autotag_option_t tagopt = remote->download_tags;
+ bool prune = false;
+ git_buf reflog_msg_buf = GIT_BUF_INIT;
+ const git_remote_callbacks *cbs = NULL;
+ const git_strarray *custom_headers = NULL;
+ const git_proxy_options *proxy = NULL;
+
+ if (opts) {
+ GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+ cbs = &opts->callbacks;
+ custom_headers = &opts->custom_headers;
+ update_fetchhead = opts->update_fetchhead;
+ tagopt = opts->download_tags;
+ GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options");
+ proxy = &opts->proxy_opts;
+ }
+
+ /* Connect and download everything */
+ if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, proxy, custom_headers)) != 0)
+ return error;
+
+ error = git_remote_download(remote, refspecs, opts);
+
+ /* We don't need to be connected anymore */
+ git_remote_disconnect(remote);
+
+ /* If the download failed, return the error */
+ if (error != 0)
+ return error;
+
+ /* Default reflog message */
+ if (reflog_message)
+ git_buf_sets(&reflog_msg_buf, reflog_message);
+ else {
+ git_buf_printf(&reflog_msg_buf, "fetch %s",
+ remote->name ? remote->name : remote->url);
+ }
+
+ /* Create "remote/foo" branches for all remote branches */
+ error = git_remote_update_tips(remote, cbs, update_fetchhead, tagopt, git_buf_cstr(&reflog_msg_buf));
+ git_buf_free(&reflog_msg_buf);
+ if (error < 0)
+ return error;
+
+ if (opts && opts->prune == GIT_FETCH_PRUNE)
+ prune = true;
+ else if (opts && opts->prune == GIT_FETCH_PRUNE_UNSPECIFIED && remote->prune_refs)
+ prune = true;
+ else if (opts && opts->prune == GIT_FETCH_NO_PRUNE)
+ prune = false;
+ else
+ prune = remote->prune_refs;
+
+ if (prune)
+ error = git_remote_prune(remote, cbs);
+
+ return error;
+}
+
+static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
+{
+ unsigned int i;
+ git_remote_head *remote_ref;
+
+ assert(update_heads && fetchspec_src);
+
+ *out = NULL;
+
+ git_vector_foreach(update_heads, i, remote_ref) {
+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {
+ *out = remote_ref;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int ref_to_update(int *update, git_buf *remote_name, git_remote *remote, git_refspec *spec, const char *ref_name)
+{
+ int error = 0;
+ git_repository *repo;
+ git_buf upstream_remote = GIT_BUF_INIT;
+ git_buf upstream_name = GIT_BUF_INIT;
+
+ repo = git_remote_owner(remote);
+
+ if ((!git_reference__is_branch(ref_name)) ||
+ !git_remote_name(remote) ||
+ (error = git_branch_upstream_remote(&upstream_remote, repo, ref_name) < 0) ||
+ git__strcmp(git_remote_name(remote), git_buf_cstr(&upstream_remote)) ||
+ (error = git_branch_upstream_name(&upstream_name, repo, ref_name)) < 0 ||
+ !git_refspec_dst_matches(spec, git_buf_cstr(&upstream_name)) ||
+ (error = git_refspec_rtransform(remote_name, spec, upstream_name.ptr)) < 0) {
+ /* Not an error if there is no upstream */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ *update = 0;
+ } else {
+ *update = 1;
+ }
+
+ git_buf_free(&upstream_remote);
+ git_buf_free(&upstream_name);
+ return error;
+}
+
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_refspec *spec, git_vector *update_heads, git_reference *ref)
+{
+ git_reference *resolved_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
+ git_config *config = NULL;
+ const char *ref_name;
+ int error = 0, update;
+
+ assert(out && spec && ref);
+
+ *out = NULL;
+
+ error = git_reference_resolve(&resolved_ref, ref);
+
+ /* If we're in an unborn branch, let's pretend nothing happened */
+ if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ ref_name = git_reference_symbolic_target(ref);
+ error = 0;
+ } else {
+ ref_name = git_reference_name(resolved_ref);
+ }
+
+ if ((error = ref_to_update(&update, &remote_name, remote, spec, ref_name)) < 0)
+ goto cleanup;
+
+ if (update)
+ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
+
+cleanup:
+ git_buf_free(&remote_name);
+ git_reference_free(resolved_ref);
+ git_config_free(config);
+ return error;
+}
+
+static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads)
+{
+ git_reference *head_ref = NULL;
+ git_fetchhead_ref *fetchhead_ref;
+ git_remote_head *remote_ref, *merge_remote_ref;
+ git_vector fetchhead_refs;
+ bool include_all_fetchheads;
+ unsigned int i = 0;
+ int error = 0;
+
+ assert(remote);
+
+ /* no heads, nothing to do */
+ if (update_heads->length == 0)
+ return 0;
+
+ if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
+ return -1;
+
+ /* Iff refspec is * (but not subdir slash star), include tags */
+ include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);
+
+ /* Determine what to merge: if refspec was a wildcard, just use HEAD */
+ if (git_refspec_is_wildcard(spec)) {
+ if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||
+ (error = remote_head_for_ref(&merge_remote_ref, remote, spec, update_heads, head_ref)) < 0)
+ goto cleanup;
+ } else {
+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
+ goto cleanup;
+ }
+
+ /* Create the FETCH_HEAD file */
+ git_vector_foreach(update_heads, i, remote_ref) {
+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);
+
+ if (!include_all_fetchheads &&
+ !git_refspec_src_matches(spec, remote_ref->name) &&
+ !merge_this_fetchhead)
+ continue;
+
+ if (git_fetchhead_ref_create(&fetchhead_ref,
+ &remote_ref->oid,
+ merge_this_fetchhead,
+ remote_ref->name,
+ git_remote_url(remote)) < 0)
+ goto cleanup;
+
+ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
+ goto cleanup;
+ }
+
+ git_fetchhead_write(remote->repo, &fetchhead_refs);
+
+cleanup:
+ for (i = 0; i < fetchhead_refs.length; ++i)
+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);
+
+ git_vector_free(&fetchhead_refs);
+ git_reference_free(head_ref);
+
+ return error;
+}
+
+/**
+ * Generate a list of candidates for pruning by getting a list of
+ * references which match the rhs of an active refspec.
+ */
+static int prune_candidates(git_vector *candidates, git_remote *remote)
+{
+ git_strarray arr = { 0 };
+ size_t i;
+ int error;
+
+ if ((error = git_reference_list(&arr, remote->repo)) < 0)
+ return error;
+
+ for (i = 0; i < arr.count; i++) {
+ const char *refname = arr.strings[i];
+ char *refname_dup;
+
+ if (!git_remote__matching_dst_refspec(remote, refname))
+ continue;
+
+ refname_dup = git__strdup(refname);
+ GITERR_CHECK_ALLOC(refname_dup);
+
+ if ((error = git_vector_insert(candidates, refname_dup)) < 0)
+ goto out;
+ }
+
+out:
+ git_strarray_free(&arr);
+ return error;
+}
+
+static int find_head(const void *_a, const void *_b)
+{
+ git_remote_head *a = (git_remote_head *) _a;
+ git_remote_head *b = (git_remote_head *) _b;
+
+ return strcmp(a->name, b->name);
+}
+
+int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks)
+{
+ size_t i, j;
+ git_vector remote_refs = GIT_VECTOR_INIT;
+ git_vector candidates = GIT_VECTOR_INIT;
+ const git_refspec *spec;
+ const char *refname;
+ int error;
+ git_oid zero_id = {{ 0 }};
+
+ if (callbacks)
+ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+
+ if ((error = ls_to_vector(&remote_refs, remote)) < 0)
+ goto cleanup;
+
+ git_vector_set_cmp(&remote_refs, find_head);
+
+ if ((error = prune_candidates(&candidates, remote)) < 0)
+ goto cleanup;
+
+ /*
+ * Remove those entries from the candidate list for which we
+ * can find a remote reference in at least one refspec.
+ */
+ git_vector_foreach(&candidates, i, refname) {
+ git_vector_foreach(&remote->active_refspecs, j, spec) {
+ git_buf buf = GIT_BUF_INIT;
+ size_t pos;
+ char *src_name;
+ git_remote_head key = {0};
+
+ if (!git_refspec_dst_matches(spec, refname))
+ continue;
+
+ if ((error = git_refspec_rtransform(&buf, spec, refname)) < 0)
+ goto cleanup;
+
+ key.name = (char *) git_buf_cstr(&buf);
+ error = git_vector_search(&pos, &remote_refs, &key);
+ git_buf_free(&buf);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ /* if we did find a source, remove it from the candiates */
+ if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0)
+ goto cleanup;
+
+ git__free(src_name);
+ break;
+ }
+ }
+
+ /*
+ * For those candidates still left in the list, we need to
+ * remove them. We do not remove symrefs, as those are for
+ * stuff like origin/HEAD which will never match, but we do
+ * not want to remove them.
+ */
+ git_vector_foreach(&candidates, i, refname) {
+ git_reference *ref;
+ git_oid id;
+
+ if (refname == NULL)
+ continue;
+
+ error = git_reference_lookup(&ref, remote->repo, refname);
+ /* as we want it gone, let's not consider this an error */
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ if (error < 0)
+ goto cleanup;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ git_reference_free(ref);
+ continue;
+ }
+
+ git_oid_cpy(&id, git_reference_target(ref));
+ error = git_reference_delete(ref);
+ git_reference_free(ref);
+ if (error < 0)
+ goto cleanup;
+
+ if (callbacks && callbacks->update_tips)
+ error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload);
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ git_vector_free(&remote_refs);
+ git_vector_free_deep(&candidates);
+ return error;
+}
+
+static int update_tips_for_spec(
+ git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ int update_fetchhead,
+ git_remote_autotag_option_t tagopt,
+ git_refspec *spec,
+ git_vector *refs,
+ const char *log_message)
+{
+ int error = 0, autotag;
+ unsigned int i = 0;
+ git_buf refname = GIT_BUF_INIT;
+ git_oid old;
+ git_odb *odb;
+ git_remote_head *head;
+ git_reference *ref;
+ git_refspec tagspec;
+ git_vector update_heads;
+
+ assert(remote);
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ return -1;
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
+
+ for (; i < refs->length; ++i) {
+ head = git_vector_get(refs, i);
+ autotag = 0;
+ git_buf_clear(&refname);
+
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if (!git_reference_is_valid_name(head->name))
+ continue;
+
+ /* If we have a tag, see if the auto-follow rules say to update it */
+ if (git_refspec_src_matches(&tagspec, head->name)) {
+ if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+
+ if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
+ autotag = 1;
+
+ git_buf_clear(&refname);
+ if (git_buf_puts(&refname, head->name) < 0)
+ goto on_error;
+ }
+ }
+
+ /* If we didn't want to auto-follow the tag, check if the refspec matches */
+ if (!autotag && git_refspec_src_matches(spec, head->name)) {
+ if (spec->dst) {
+ if (git_refspec_transform(&refname, spec, head->name) < 0)
+ goto on_error;
+ } else {
+ /*
+ * no rhs mans store it in FETCH_HEAD, even if we don't
+ update anything else.
+ */
+ if ((error = git_vector_insert(&update_heads, head)) < 0)
+ goto on_error;
+
+ continue;
+ }
+ }
+
+ /* If we still don't have a refname, we don't want it */
+ if (git_buf_len(&refname) == 0) {
+ continue;
+ }
+
+ /* In autotag mode, only create tags for objects already in db */
+ if (autotag && !git_odb_exists(odb, &head->oid))
+ continue;
+
+ if (!autotag && git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (error == GIT_ENOTFOUND) {
+ memset(&old, 0, GIT_OID_RAWSZ);
+
+ if (autotag && git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+ }
+
+ if (!git_oid__cmp(&old, &head->oid))
+ continue;
+
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
+ log_message);
+
+ if (error == GIT_EEXISTS)
+ continue;
+
+ if (error < 0)
+ goto on_error;
+
+ git_reference_free(ref);
+
+ if (callbacks && callbacks->update_tips != NULL) {
+ if (callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload) < 0)
+ goto on_error;
+ }
+ }
+
+ if (update_fetchhead &&
+ (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0)
+ goto on_error;
+
+ git_vector_free(&update_heads);
+ git_refspec__free(&tagspec);
+ git_buf_free(&refname);
+ return 0;
+
+on_error:
+ git_vector_free(&update_heads);
+ git_refspec__free(&tagspec);
+ git_buf_free(&refname);
+ return -1;
+
+}
+
+/**
+ * Iteration over the three vectors, with a pause whenever we find a match
+ *
+ * On each stop, we store the iteration stat in the inout i,j,k
+ * parameters, and return the currently matching passive refspec as
+ * well as the head which we matched.
+ */
+static int next_head(const git_remote *remote, git_vector *refs,
+ git_refspec **out_spec, git_remote_head **out_head,
+ size_t *out_i, size_t *out_j, size_t *out_k)
+{
+ const git_vector *active, *passive;
+ git_remote_head *head;
+ git_refspec *spec, *passive_spec;
+ size_t i, j, k;
+
+ active = &remote->active_refspecs;
+ passive = &remote->passive_refspecs;
+
+ i = *out_i;
+ j = *out_j;
+ k = *out_k;
+
+ for (; i < refs->length; i++) {
+ head = git_vector_get(refs, i);
+
+ if (!git_reference_is_valid_name(head->name))
+ continue;
+
+ for (; j < active->length; j++) {
+ spec = git_vector_get(active, j);
+
+ if (!git_refspec_src_matches(spec, head->name))
+ continue;
+
+ for (; k < passive->length; k++) {
+ passive_spec = git_vector_get(passive, k);
+
+ if (!git_refspec_src_matches(passive_spec, head->name))
+ continue;
+
+ *out_spec = passive_spec;
+ *out_head = head;
+ *out_i = i;
+ *out_j = j;
+ *out_k = k + 1;
+ return 0;
+
+ }
+ k = 0;
+ }
+ j = 0;
+ }
+
+ return GIT_ITEROVER;
+}
+
+static int opportunistic_updates(const git_remote *remote, const git_remote_callbacks *callbacks,
+ git_vector *refs, const char *msg)
+{
+ size_t i, j, k;
+ git_refspec *spec;
+ git_remote_head *head;
+ git_reference *ref;
+ git_buf refname = GIT_BUF_INIT;
+ int error = 0;
+
+ i = j = k = 0;
+
+ while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) {
+ git_oid old = {{ 0 }};
+ /*
+ * If we got here, there is a refspec which was used
+ * for fetching which matches the source of one of the
+ * passive refspecs, so we should update that
+ * remote-tracking branch, but not add it to
+ * FETCH_HEAD
+ */
+
+ git_buf_clear(&refname);
+ if ((error = git_refspec_transform(&refname, spec, head->name)) < 0)
+ goto cleanup;
+
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if (!git_oid_cmp(&old, &head->oid))
+ continue;
+
+ /* If we did find a current reference, make sure we haven't lost a race */
+ if (error)
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, true, msg);
+ else
+ error = git_reference_create_matching(&ref, remote->repo, refname.ptr, &head->oid, true, &old, msg);
+ git_reference_free(ref);
+ if (error < 0)
+ goto cleanup;
+
+ if (callbacks && callbacks->update_tips != NULL) {
+ if (callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload) < 0)
+ goto cleanup;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+cleanup:
+ git_buf_free(&refname);
+ return error;
+}
+
+int git_remote_update_tips(
+ git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ int update_fetchhead,
+ git_remote_autotag_option_t download_tags,
+ const char *reflog_message)
+{
+ git_refspec *spec, tagspec;
+ git_vector refs = GIT_VECTOR_INIT;
+ git_remote_autotag_option_t tagopt;
+ int error;
+ size_t i;
+
+ /* push has its own logic hidden away in the push object */
+ if (remote->push) {
+ return git_push_update_tips(remote->push, callbacks);
+ }
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+
+ if ((error = ls_to_vector(&refs, remote)) < 0)
+ goto out;
+
+ if (download_tags == GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED)
+ tagopt = remote->download_tags;
+ else
+ tagopt = download_tags;
+
+ if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0)
+ goto out;
+ }
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0)
+ goto out;
+ }
+
+ /* only try to do opportunisitic updates if the refpec lists differ */
+ if (remote->passed_refspecs)
+ error = opportunistic_updates(remote, callbacks, &refs, reflog_message);
+
+out:
+ git_vector_free(&refs);
+ git_refspec__free(&tagspec);
+ return error;
+}
+
+int git_remote_connected(const git_remote *remote)
+{
+ assert(remote);
+
+ if (!remote->transport || !remote->transport->is_connected)
+ return 0;
+
+ /* Ask the transport if it's connected. */
+ return remote->transport->is_connected(remote->transport);
+}
+
+void git_remote_stop(git_remote *remote)
+{
+ assert(remote);
+
+ if (remote->transport && remote->transport->cancel)
+ remote->transport->cancel(remote->transport);
+}
+
+void git_remote_disconnect(git_remote *remote)
+{
+ assert(remote);
+
+ if (git_remote_connected(remote))
+ remote->transport->close(remote->transport);
+}
+
+void git_remote_free(git_remote *remote)
+{
+ if (remote == NULL)
+ return;
+
+ if (remote->transport != NULL) {
+ git_remote_disconnect(remote);
+
+ remote->transport->free(remote->transport);
+ remote->transport = NULL;
+ }
+
+ git_vector_free(&remote->refs);
+
+ free_refspecs(&remote->refspecs);
+ git_vector_free(&remote->refspecs);
+
+ free_refspecs(&remote->active_refspecs);
+ git_vector_free(&remote->active_refspecs);
+
+ free_refspecs(&remote->passive_refspecs);
+ git_vector_free(&remote->passive_refspecs);
+
+ git_push_free(remote->push);
+ git__free(remote->url);
+ git__free(remote->pushurl);
+ git__free(remote->name);
+ git__free(remote);
+}
+
+static int remote_list_cb(const git_config_entry *entry, void *payload)
+{
+ git_vector *list = payload;
+ const char *name = entry->name + strlen("remote.");
+ size_t namelen = strlen(name);
+ char *remote_name;
+
+ /* we know name matches "remote.<stuff>.(push)?url" */
+
+ if (!strcmp(&name[namelen - 4], ".url"))
+ remote_name = git__strndup(name, namelen - 4); /* strip ".url" */
+ else
+ remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */
+ GITERR_CHECK_ALLOC(remote_name);
+
+ return git_vector_insert(list, remote_name);
+}
+
+int git_remote_list(git_strarray *remotes_list, git_repository *repo)
+{
+ int error;
+ git_config *cfg;
+ git_vector list = GIT_VECTOR_INIT;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0)
+ return error;
+
+ error = git_config_foreach_match(
+ cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list);
+
+ if (error < 0) {
+ git_vector_free_deep(&list);
+ return error;
+ }
+
+ git_vector_uniq(&list, git__free);
+
+ remotes_list->strings =
+ (char **)git_vector_detach(&remotes_list->count, NULL, &list);
+
+ return 0;
+}
+
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
+}
+
+git_remote_autotag_option_t git_remote_autotag(const git_remote *remote)
+{
+ return remote->download_tags;
+}
+
+int git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value)
+{
+ git_buf var = GIT_BUF_INIT;
+ git_config *config;
+ int error;
+
+ assert(repo && remote);
+
+ if ((error = ensure_remote_name_is_valid(remote)) < 0)
+ return error;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ if ((error = git_buf_printf(&var, CONFIG_TAGOPT_FMT, remote)))
+ return error;
+
+ switch (value) {
+ case GIT_REMOTE_DOWNLOAD_TAGS_NONE:
+ error = git_config_set_string(config, var.ptr, "--no-tags");
+ break;
+ case GIT_REMOTE_DOWNLOAD_TAGS_ALL:
+ error = git_config_set_string(config, var.ptr, "--tags");
+ break;
+ case GIT_REMOTE_DOWNLOAD_TAGS_AUTO:
+ error = git_config_delete_entry(config, var.ptr);
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Invalid value for the tagopt setting");
+ error = -1;
+ }
+
+ git_buf_free(&var);
+ return error;
+}
+
+int git_remote_prune_refs(const git_remote *remote)
+{
+ return remote->prune_refs;
+}
+
+static int rename_remote_config_section(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_buf old_section_name = GIT_BUF_INIT,
+ new_section_name = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0)
+ goto cleanup;
+
+ if (new_name &&
+ (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0))
+ goto cleanup;
+
+ error = git_config_rename_section(
+ repo,
+ git_buf_cstr(&old_section_name),
+ new_name ? git_buf_cstr(&new_section_name) : NULL);
+
+cleanup:
+ git_buf_free(&old_section_name);
+ git_buf_free(&new_section_name);
+
+ return error;
+}
+
+struct update_data {
+ git_config *config;
+ const char *old_remote_name;
+ const char *new_remote_name;
+};
+
+static int update_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ struct update_data *data = (struct update_data *)payload;
+
+ if (strcmp(entry->value, data->old_remote_name))
+ return 0;
+
+ return git_config_set_string(
+ data->config, entry->name, data->new_remote_name);
+}
+
+static int update_branch_remote_config_entry(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ int error;
+ struct update_data data = { NULL };
+
+ if ((error = git_repository_config__weakptr(&data.config, repo)) < 0)
+ return error;
+
+ data.old_remote_name = old_name;
+ data.new_remote_name = new_name;
+
+ return git_config_foreach_match(
+ data.config, "branch\\..+\\.remote", update_config_entries_cb, &data);
+}
+
+static int rename_one_remote_reference(
+ git_reference *reference_in,
+ const char *old_remote_name,
+ const char *new_remote_name)
+{
+ int error;
+ git_reference *ref = NULL, *dummy = NULL;
+ git_buf namespace = GIT_BUF_INIT, old_namespace = GIT_BUF_INIT;
+ git_buf new_name = GIT_BUF_INIT;
+ git_buf log_message = GIT_BUF_INIT;
+ size_t pfx_len;
+ const char *target;
+
+ if ((error = git_buf_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0)
+ return error;
+
+ pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1;
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&log_message,
+ "renamed remote %s to %s",
+ old_remote_name, new_remote_name)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_rename(&ref, reference_in, git_buf_cstr(&new_name), 1,
+ git_buf_cstr(&log_message))) < 0)
+ goto cleanup;
+
+ if (git_reference_type(ref) != GIT_REF_SYMBOLIC)
+ goto cleanup;
+
+ /* Handle refs like origin/HEAD -> origin/master */
+ target = git_reference_symbolic_target(ref);
+ if ((error = git_buf_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0)
+ goto cleanup;
+
+ if (git__prefixcmp(target, old_namespace.ptr))
+ goto cleanup;
+
+ git_buf_clear(&new_name);
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, target + pfx_len)) < 0)
+ goto cleanup;
+
+ error = git_reference_symbolic_set_target(&dummy, ref, git_buf_cstr(&new_name),
+ git_buf_cstr(&log_message));
+
+ git_reference_free(dummy);
+
+cleanup:
+ git_reference_free(reference_in);
+ git_reference_free(ref);
+ git_buf_free(&namespace);
+ git_buf_free(&old_namespace);
+ git_buf_free(&new_name);
+ git_buf_free(&log_message);
+ return error;
+}
+
+static int rename_remote_references(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ git_reference *ref;
+ git_reference_iterator *iter;
+
+ if ((error = git_buf_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0)
+ return error;
+
+ error = git_reference_iterator_glob_new(&iter, repo, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0)
+ break;
+ }
+
+ git_reference_iterator_free(iter);
+
+ return (error == GIT_ITEROVER) ? 0 : error;
+}
+
+static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name)
+{
+ git_config *config;
+ git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT;
+ const git_refspec *spec;
+ size_t i;
+ int error = 0;
+
+ if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0)
+ return error;
+
+ if ((error = git_vector_init(problems, 1, NULL)) < 0)
+ return error;
+
+ if ((error = git_buf_printf(
+ &base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0)
+ return error;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ /* Does the dst part of the refspec follow the expected format? */
+ if (strcmp(git_buf_cstr(&base), spec->string)) {
+ char *dup;
+
+ dup = git__strdup(spec->string);
+ GITERR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(problems, dup)) < 0)
+ break;
+
+ continue;
+ }
+
+ /* If we do want to move it to the new section */
+
+ git_buf_clear(&val);
+ git_buf_clear(&var);
+
+ if (git_buf_printf(
+ &val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0 ||
+ git_buf_printf(&var, "remote.%s.fetch", new_name) < 0)
+ {
+ error = -1;
+ break;
+ }
+
+ if ((error = git_config_set_string(
+ config, git_buf_cstr(&var), git_buf_cstr(&val))) < 0)
+ break;
+ }
+
+ git_buf_free(&base);
+ git_buf_free(&var);
+ git_buf_free(&val);
+
+ if (error < 0) {
+ char *str;
+ git_vector_foreach(problems, i, str)
+ git__free(str);
+
+ git_vector_free(problems);
+ }
+
+ return error;
+}
+
+int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, const char *new_name)
+{
+ int error;
+ git_vector problem_refspecs = GIT_VECTOR_INIT;
+ git_remote *remote = NULL;
+
+ assert(out && repo && name && new_name);
+
+ if ((error = git_remote_lookup(&remote, repo, name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_name_is_valid(new_name)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_remote_doesnot_exist(repo, new_name)) < 0)
+ goto cleanup;
+
+ if ((error = rename_remote_config_section(repo, name, new_name)) < 0)
+ goto cleanup;
+
+ if ((error = update_branch_remote_config_entry(repo, name, new_name)) < 0)
+ goto cleanup;
+
+ if ((error = rename_remote_references(repo, name, new_name)) < 0)
+ goto cleanup;
+
+ if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0)
+ goto cleanup;
+
+ out->count = problem_refspecs.length;
+ out->strings = (char **) problem_refspecs.contents;
+
+cleanup:
+ if (error < 0)
+ git_vector_free(&problem_refspecs);
+
+ git_remote_free(remote);
+ return error;
+}
+
+int git_remote_is_valid_name(
+ const char *remote_name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!remote_name || *remote_name == '\0')
+ return 0;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
+
+ git_buf_free(&buf);
+ git_refspec__free(&refspec);
+
+ giterr_clear();
+ return error == 0;
+}
+
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_src_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_dst_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec)
+{
+ return write_add_refspec(repo, remote, refspec, true);
+}
+
+int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec)
+{
+ return write_add_refspec(repo, remote, refspec, false);
+}
+
+static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push)
+{
+ size_t i;
+ git_vector refspecs;
+ git_refspec *spec;
+ char *dup;
+
+ if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0)
+ return -1;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push != push)
+ continue;
+
+ if ((dup = git__strdup(spec->string)) == NULL)
+ goto on_error;
+
+ if (git_vector_insert(&refspecs, dup) < 0) {
+ git__free(dup);
+ goto on_error;
+ }
+ }
+
+ array->strings = (char **)refspecs.contents;
+ array->count = refspecs.length;
+
+ return 0;
+
+on_error:
+ git_vector_free_deep(&refspecs);
+
+ return -1;
+}
+
+int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote)
+{
+ return copy_refspecs(array, remote, false);
+}
+
+int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote)
+{
+ return copy_refspecs(array, remote, true);
+}
+
+size_t git_remote_refspec_count(const git_remote *remote)
+{
+ return remote->refspecs.length;
+}
+
+const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n)
+{
+ return git_vector_get(&remote->refspecs, n);
+}
+
+int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT);
+ return 0;
+}
+
+/* asserts a branch.<foo>.remote format */
+static const char *name_offset(size_t *len_out, const char *name)
+{
+ size_t prefix_len;
+ const char *dot;
+
+ prefix_len = strlen("remote.");
+ dot = strchr(name + prefix_len, '.');
+
+ assert(dot);
+
+ *len_out = dot - name - prefix_len;
+ return name + prefix_len;
+}
+
+static int remove_branch_config_related_entries(
+ git_repository *repo,
+ const char *remote_name)
+{
+ int error;
+ git_config *config;
+ git_config_entry *entry;
+ git_config_iterator *iter;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0)
+ return error;
+
+ /* find any branches with us as upstream and remove that config */
+ while ((error = git_config_next(&entry, iter)) == 0) {
+ const char *branch;
+ size_t branch_len;
+
+ if (strcmp(remote_name, entry->value))
+ continue;
+
+ branch = name_offset(&branch_len, entry->name);
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch) < 0)
+ break;
+
+ if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) {
+ if (error != GIT_ENOTFOUND)
+ break;
+ giterr_clear();
+ }
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch) < 0)
+ break;
+
+ if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) {
+ if (error != GIT_ENOTFOUND)
+ break;
+ giterr_clear();
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_buf_free(&buf);
+ git_config_iterator_free(iter);
+ return error;
+}
+
+static int remove_refs(git_repository *repo, const git_refspec *spec)
+{
+ git_reference_iterator *iter = NULL;
+ git_vector refs;
+ const char *name;
+ char *dup;
+ int error;
+ size_t i;
+
+ if ((error = git_vector_init(&refs, 8, NULL)) < 0)
+ return error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_reference_next_name(&name, iter)) == 0) {
+ if (!git_refspec_dst_matches(spec, name))
+ continue;
+
+ dup = git__strdup(name);
+ if (!dup) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_vector_insert(&refs, dup)) < 0)
+ goto cleanup;
+ }
+ if (error == GIT_ITEROVER)
+ error = 0;
+ if (error < 0)
+ goto cleanup;
+
+ git_vector_foreach(&refs, i, name) {
+ if ((error = git_reference_remove(repo, name)) < 0)
+ break;
+ }
+
+cleanup:
+ git_reference_iterator_free(iter);
+ git_vector_foreach(&refs, i, dup) {
+ git__free(dup);
+ }
+ git_vector_free(&refs);
+ return error;
+}
+
+static int remove_remote_tracking(git_repository *repo, const char *remote_name)
+{
+ git_remote *remote;
+ int error;
+ size_t i, count;
+
+ /* we want to use what's on the config, regardless of changes to the instance in memory */
+ if ((error = git_remote_lookup(&remote, repo, remote_name)) < 0)
+ return error;
+
+ count = git_remote_refspec_count(remote);
+ for (i = 0; i < count; i++) {
+ const git_refspec *refspec = git_remote_get_refspec(remote, i);
+
+ /* shouldn't ever actually happen */
+ if (refspec == NULL)
+ continue;
+
+ if ((error = remove_refs(repo, refspec)) < 0)
+ break;
+ }
+
+ git_remote_free(remote);
+ return error;
+}
+
+int git_remote_delete(git_repository *repo, const char *name)
+{
+ int error;
+
+ assert(repo && name);
+
+ if ((error = remove_branch_config_related_entries(repo, name)) < 0 ||
+ (error = remove_remote_tracking(repo, name)) < 0 ||
+ (error = rename_remote_config_section(repo, name, NULL)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_remote_default_branch(git_buf *out, git_remote *remote)
+{
+ const git_remote_head **heads;
+ const git_remote_head *guess = NULL;
+ const git_oid *head_id;
+ size_t heads_len, i;
+ int error;
+
+ assert(out);
+
+ if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0)
+ return error;
+
+ if (heads_len == 0)
+ return GIT_ENOTFOUND;
+
+ if (strcmp(heads[0]->name, GIT_HEAD_FILE))
+ return GIT_ENOTFOUND;
+
+ git_buf_sanitize(out);
+ /* the first one must be HEAD so if that has the symref info, we're done */
+ if (heads[0]->symref_target)
+ return git_buf_puts(out, heads[0]->symref_target);
+
+ /*
+ * If there's no symref information, we have to look over them
+ * and guess. We return the first match unless the master
+ * branch is a candidate. Then we return the master branch.
+ */
+ head_id = &heads[0]->oid;
+
+ for (i = 1; i < heads_len; i++) {
+ if (git_oid_cmp(head_id, &heads[i]->oid))
+ continue;
+
+ if (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR))
+ continue;
+
+ if (!guess) {
+ guess = heads[i];
+ continue;
+ }
+
+ if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) {
+ guess = heads[i];
+ break;
+ }
+ }
+
+ if (!guess)
+ return GIT_ENOTFOUND;
+
+ return git_buf_puts(out, guess->name);
+}
+
+int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts)
+{
+ size_t i;
+ int error;
+ git_push *push;
+ git_refspec *spec;
+ const git_remote_callbacks *cbs = NULL;
+ const git_strarray *custom_headers = NULL;
+ const git_proxy_options *proxy = NULL;
+
+ assert(remote);
+
+ if (opts) {
+ cbs = &opts->callbacks;
+ custom_headers = &opts->custom_headers;
+ proxy = &opts->proxy_opts;
+ }
+
+ if (!git_remote_connected(remote) &&
+ (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0)
+ goto cleanup;
+
+ free_refspecs(&remote->active_refspecs);
+ if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0)
+ goto cleanup;
+
+ if (remote->push) {
+ git_push_free(remote->push);
+ remote->push = NULL;
+ }
+
+ if ((error = git_push_new(&remote->push, remote)) < 0)
+ return error;
+
+ push = remote->push;
+
+ if (opts && (error = git_push_set_options(push, opts)) < 0)
+ goto cleanup;
+
+ if (refspecs && refspecs->count > 0) {
+ for (i = 0; i < refspecs->count; i++) {
+ if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0)
+ goto cleanup;
+ }
+ } else {
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (!spec->push)
+ continue;
+ if ((error = git_push_add_refspec(push, spec->string)) < 0)
+ goto cleanup;
+ }
+ }
+
+ if ((error = git_push_finish(push, cbs)) < 0)
+ goto cleanup;
+
+ if (cbs && cbs->push_update_reference &&
+ (error = git_push_status_foreach(push, cbs->push_update_reference, cbs->payload)) < 0)
+ goto cleanup;
+
+cleanup:
+ return error;
+}
+
+int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts)
+{
+ int error;
+ const git_remote_callbacks *cbs = NULL;
+ const git_strarray *custom_headers = NULL;
+ const git_proxy_options *proxy = NULL;
+
+ if (opts) {
+ GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+ cbs = &opts->callbacks;
+ custom_headers = &opts->custom_headers;
+ GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options");
+ proxy = &opts->proxy_opts;
+ }
+
+ assert(remote && refspecs);
+
+ if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0)
+ return error;
+
+ if ((error = git_remote_upload(remote, refspecs, opts)) < 0)
+ return error;
+
+ error = git_remote_update_tips(remote, cbs, 0, 0, NULL);
+
+ git_remote_disconnect(remote);
+ return error;
+}
+
+#define PREFIX "url"
+#define SUFFIX_FETCH "insteadof"
+#define SUFFIX_PUSH "pushinsteadof"
+
+char *apply_insteadof(git_config *config, const char *url, int direction)
+{
+ size_t match_length, prefix_length, suffix_length;
+ char *replacement = NULL;
+ const char *regexp;
+
+ git_buf result = GIT_BUF_INIT;
+ git_config_entry *entry;
+ git_config_iterator *iter;
+
+ assert(config);
+ assert(url);
+ assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH);
+
+ /* Add 1 to prefix/suffix length due to the additional escaped dot */
+ prefix_length = strlen(PREFIX) + 1;
+ if (direction == GIT_DIRECTION_FETCH) {
+ regexp = PREFIX "\\..*\\." SUFFIX_FETCH;
+ suffix_length = strlen(SUFFIX_FETCH) + 1;
+ } else {
+ regexp = PREFIX "\\..*\\." SUFFIX_PUSH;
+ suffix_length = strlen(SUFFIX_PUSH) + 1;
+ }
+
+ if (git_config_iterator_glob_new(&iter, config, regexp) < 0)
+ return NULL;
+
+ match_length = 0;
+ while (git_config_next(&entry, iter) == 0) {
+ size_t n, replacement_length;
+
+ /* Check if entry value is a prefix of URL */
+ if (git__prefixcmp(url, entry->value))
+ continue;
+ /* Check if entry value is longer than previous
+ * prefixes */
+ if ((n = strlen(entry->value)) <= match_length)
+ continue;
+
+ git__free(replacement);
+ match_length = n;
+
+ /* Cut off prefix and suffix of the value */
+ replacement_length =
+ strlen(entry->name) - (prefix_length + suffix_length);
+ replacement = git__strndup(entry->name + prefix_length,
+ replacement_length);
+ }
+
+ git_config_iterator_free(iter);
+
+ if (match_length == 0)
+ return git__strdup(url);
+
+ git_buf_printf(&result, "%s%s", replacement, url + match_length);
+
+ git__free(replacement);
+
+ return result.ptr;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_remote_h__
+#define INCLUDE_remote_h__
+
+#include "git2/remote.h"
+#include "git2/transport.h"
+#include "git2/sys/transport.h"
+
+#include "refspec.h"
+#include "vector.h"
+
+#define GIT_REMOTE_ORIGIN "origin"
+
+struct git_remote {
+ char *name;
+ char *url;
+ char *pushurl;
+ git_vector refs;
+ git_vector refspecs;
+ git_vector active_refspecs;
+ git_vector passive_refspecs;
+ git_transport *transport;
+ git_repository *repo;
+ git_push *push;
+ git_transfer_progress stats;
+ unsigned int need_pack;
+ git_remote_autotag_option_t download_tags;
+ int prune_refs;
+ int passed_refspecs;
+};
+
+const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url);
+
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname);
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_repo_template_h__
+#define INCLUDE_repo_template_h__
+
+#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"
+#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"
+
+#define GIT_HOOKS_DIR "hooks/"
+#define GIT_HOOKS_DIR_MODE 0777
+
+#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"
+#define GIT_HOOKS_README_MODE 0777
+#define GIT_HOOKS_README_CONTENT \
+"#!/bin/sh\n"\
+"#\n"\
+"# Place appropriately named executable hook scripts into this directory\n"\
+"# to intercept various actions that git takes. See `git help hooks` for\n"\
+"# more information.\n"
+
+#define GIT_INFO_DIR "info/"
+#define GIT_INFO_DIR_MODE 0777
+
+#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"
+#define GIT_INFO_EXCLUDE_MODE 0666
+#define GIT_INFO_EXCLUDE_CONTENT \
+"# File patterns to ignore; see `git help ignore` for more information.\n"\
+"# Lines that start with '#' are comments.\n"
+
+#define GIT_DESC_FILE "description"
+#define GIT_DESC_MODE 0666
+#define GIT_DESC_CONTENT \
+"Unnamed repository; edit this file 'description' to name the repository.\n"
+
+typedef struct {
+ const char *path;
+ mode_t mode;
+ const char *content;
+} repo_template_item;
+
+static repo_template_item repo_template[] = {
+ { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */
+ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */
+ { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */
+ { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */
+ { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */
+ { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */
+ { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },
+ { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },
+ { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },
+ { NULL, 0, NULL }
+};
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include <ctype.h>
+
+#include "git2/object.h"
+#include "git2/refdb.h"
+#include "git2/sys/repository.h"
+
+#include "common.h"
+#include "repository.h"
+#include "commit.h"
+#include "tag.h"
+#include "blob.h"
+#include "fileops.h"
+#include "sysdir.h"
+#include "filebuf.h"
+#include "index.h"
+#include "config.h"
+#include "refs.h"
+#include "filter.h"
+#include "odb.h"
+#include "remote.h"
+#include "merge.h"
+#include "diff_driver.h"
+#include "annotated_commit.h"
+
+#ifdef GIT_WIN32
+# include "win32/w32_util.h"
+#endif
+
+static int check_repositoryformatversion(git_config *config);
+
+#define GIT_FILE_CONTENT_PREFIX "gitdir:"
+
+#define GIT_BRANCH_MASTER "master"
+
+#define GIT_REPO_VERSION 0
+
+git_buf git_repository__reserved_names_win32[] = {
+ { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) },
+ { GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) }
+};
+size_t git_repository__reserved_names_win32_len = 2;
+
+git_buf git_repository__reserved_names_posix[] = {
+ { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) },
+};
+size_t git_repository__reserved_names_posix_len = 1;
+
+static void set_odb(git_repository *repo, git_odb *odb)
+{
+ if (odb) {
+ GIT_REFCOUNT_OWN(odb, repo);
+ GIT_REFCOUNT_INC(odb);
+ }
+
+ if ((odb = git__swap(repo->_odb, odb)) != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
+ }
+}
+
+static void set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ if (refdb) {
+ GIT_REFCOUNT_OWN(refdb, repo);
+ GIT_REFCOUNT_INC(refdb);
+ }
+
+ if ((refdb = git__swap(repo->_refdb, refdb)) != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
+ }
+}
+
+static void set_config(git_repository *repo, git_config *config)
+{
+ if (config) {
+ GIT_REFCOUNT_OWN(config, repo);
+ GIT_REFCOUNT_INC(config);
+ }
+
+ if ((config = git__swap(repo->_config, config)) != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
+ }
+
+ git_repository__cvar_cache_clear(repo);
+}
+
+static void set_index(git_repository *repo, git_index *index)
+{
+ if (index) {
+ GIT_REFCOUNT_OWN(index, repo);
+ GIT_REFCOUNT_INC(index);
+ }
+
+ if ((index = git__swap(repo->_index, index)) != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
+ }
+}
+
+void git_repository__cleanup(git_repository *repo)
+{
+ assert(repo);
+
+ git_cache_clear(&repo->objects);
+ git_attr_cache_flush(repo);
+
+ set_config(repo, NULL);
+ set_index(repo, NULL);
+ set_odb(repo, NULL);
+ set_refdb(repo, NULL);
+}
+
+void git_repository_free(git_repository *repo)
+{
+ size_t i;
+
+ if (repo == NULL)
+ return;
+
+ git_repository__cleanup(repo);
+
+ git_cache_free(&repo->objects);
+
+ git_diff_driver_registry_free(repo->diff_drivers);
+ repo->diff_drivers = NULL;
+
+ for (i = 0; i < repo->reserved_names.size; i++)
+ git_buf_free(git_array_get(repo->reserved_names, i));
+ git_array_clear(repo->reserved_names);
+
+ git__free(repo->path_gitlink);
+ git__free(repo->path_repository);
+ git__free(repo->workdir);
+ git__free(repo->namespace);
+ git__free(repo->ident_name);
+ git__free(repo->ident_email);
+
+ git__memzero(repo, sizeof(*repo));
+ git__free(repo);
+}
+
+/*
+ * Git repository open methods
+ *
+ * Open a repository object from its path
+ */
+static bool valid_repository_path(git_buf *repository_path)
+{
+ /* Check OBJECTS_DIR first, since it will generate the longest path name */
+ if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)
+ return false;
+
+ /* Ensure HEAD file exists */
+ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)
+ return false;
+
+ if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)
+ return false;
+
+ return true;
+}
+
+static git_repository *repository_alloc(void)
+{
+ git_repository *repo = git__calloc(1, sizeof(git_repository));
+
+ if (repo == NULL ||
+ git_cache_init(&repo->objects) < 0)
+ goto on_error;
+
+ git_array_init_to_size(repo->reserved_names, 4);
+ if (!repo->reserved_names.ptr)
+ goto on_error;
+
+ /* set all the entries in the cvar cache to `unset` */
+ git_repository__cvar_cache_clear(repo);
+
+ return repo;
+
+on_error:
+ if (repo)
+ git_cache_free(&repo->objects);
+
+ git__free(repo);
+ return NULL;
+}
+
+int git_repository_new(git_repository **out)
+{
+ git_repository *repo;
+
+ *out = repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ repo->is_bare = 1;
+
+ return 0;
+}
+
+static int load_config_data(git_repository *repo, const git_config *config)
+{
+ int is_bare;
+
+ /* Try to figure out if it's bare, default to non-bare if it's not set */
+ if (git_config_get_bool(&is_bare, config, "core.bare") < 0)
+ repo->is_bare = 0;
+ else
+ repo->is_bare = is_bare;
+
+ return 0;
+}
+
+static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
+{
+ int error;
+ git_config_entry *ce;
+ git_buf worktree = GIT_BUF_INIT;
+
+ if (repo->is_bare)
+ return 0;
+
+ if ((error = git_config__lookup_entry(
+ &ce, config, "core.worktree", false)) < 0)
+ return error;
+
+ if (ce && ce->value) {
+ if ((error = git_path_prettify_dir(
+ &worktree, ce->value, repo->path_repository)) < 0)
+ goto cleanup;
+
+ repo->workdir = git_buf_detach(&worktree);
+ }
+ else if (parent_path && git_path_isdir(parent_path->ptr))
+ repo->workdir = git_buf_detach(parent_path);
+ else {
+ if (git_path_dirname_r(&worktree, repo->path_repository) < 0 ||
+ git_path_to_dir(&worktree) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ repo->workdir = git_buf_detach(&worktree);
+ }
+
+ GITERR_CHECK_ALLOC(repo->workdir);
+cleanup:
+ git_config_entry_free(ce);
+ return error;
+}
+
+/*
+ * This function returns furthest offset into path where a ceiling dir
+ * is found, so we can stop processing the path at that point.
+ *
+ * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on
+ * the stack could remove directories name limits, but at the cost of doing
+ * repeated malloc/frees inside the loop below, so let's not do it now.
+ */
+static size_t find_ceiling_dir_offset(
+ const char *path,
+ const char *ceiling_directories)
+{
+ char buf[GIT_PATH_MAX + 1];
+ char buf2[GIT_PATH_MAX + 1];
+ const char *ceil, *sep;
+ size_t len, max_len = 0, min_len;
+
+ assert(path);
+
+ min_len = (size_t)(git_path_root(path) + 1);
+
+ if (ceiling_directories == NULL || min_len == 0)
+ return min_len;
+
+ for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) {
+ for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++);
+ len = sep - ceil;
+
+ if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1)
+ continue;
+
+ strncpy(buf, ceil, len);
+ buf[len] = '\0';
+
+ if (p_realpath(buf, buf2) == NULL)
+ continue;
+
+ len = strlen(buf2);
+ if (len > 0 && buf2[len-1] == '/')
+ buf[--len] = '\0';
+
+ if (!strncmp(path, buf2, len) &&
+ (path[len] == '/' || !path[len]) &&
+ len > max_len)
+ {
+ max_len = len;
+ }
+ }
+
+ return (max_len <= min_len ? min_len : max_len);
+}
+
+/*
+ * Read the contents of `file_path` and set `path_out` to the repo dir that
+ * it points to. Before calling, set `path_out` to the base directory that
+ * should be used if the contents of `file_path` are a relative path.
+ */
+static int read_gitfile(git_buf *path_out, const char *file_path)
+{
+ int error = 0;
+ git_buf file = GIT_BUF_INIT;
+ size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX);
+
+ assert(path_out && file_path);
+
+ if (git_futils_readbuffer(&file, file_path) < 0)
+ return -1;
+
+ git_buf_rtrim(&file);
+ /* apparently on Windows, some people use backslashes in paths */
+ git_path_mkposix(file.ptr);
+
+ if (git_buf_len(&file) <= prefix_len ||
+ memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)
+ {
+ giterr_set(GITERR_REPOSITORY,
+ "The `.git` file at '%s' is malformed", file_path);
+ error = -1;
+ }
+ else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) {
+ const char *gitlink = git_buf_cstr(&file) + prefix_len;
+ while (*gitlink && git__isspace(*gitlink)) gitlink++;
+
+ error = git_path_prettify_dir(
+ path_out, gitlink, git_buf_cstr(path_out));
+ }
+
+ git_buf_free(&file);
+ return error;
+}
+
+static int find_repo(
+ git_buf *repo_path,
+ git_buf *parent_path,
+ git_buf *link_path,
+ const char *start_path,
+ uint32_t flags,
+ const char *ceiling_dirs)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ git_buf repo_link = GIT_BUF_INIT;
+ struct stat st;
+ dev_t initial_device = 0;
+ int min_iterations;
+ bool in_dot_git;
+ size_t ceiling_offset = 0;
+
+ git_buf_free(repo_path);
+
+ error = git_path_prettify(&path, start_path, NULL);
+ if (error < 0)
+ return error;
+
+ /* in_dot_git toggles each loop:
+ * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a
+ * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we
+ * assume we started with /a/b/c.git and don't append .git the first
+ * time through.
+ * min_iterations indicates the number of iterations left before going
+ * further counts as a search. */
+ if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) {
+ in_dot_git = true;
+ min_iterations = 1;
+ } else {
+ in_dot_git = false;
+ min_iterations = 2;
+ }
+
+ for (;;) {
+ if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) {
+ if (!in_dot_git) {
+ error = git_buf_joinpath(&path, path.ptr, DOT_GIT);
+ if (error < 0)
+ break;
+ }
+ in_dot_git = !in_dot_git;
+ }
+
+ if (p_stat(path.ptr, &st) == 0) {
+ /* check that we have not crossed device boundaries */
+ if (initial_device == 0)
+ initial_device = st.st_dev;
+ else if (st.st_dev != initial_device &&
+ !(flags & GIT_REPOSITORY_OPEN_CROSS_FS))
+ break;
+
+ if (S_ISDIR(st.st_mode)) {
+ if (valid_repository_path(&path)) {
+ git_path_to_dir(&path);
+ git_buf_set(repo_path, path.ptr, path.size);
+ break;
+ }
+ }
+ else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) {
+ error = read_gitfile(&repo_link, path.ptr);
+ if (error < 0)
+ break;
+ if (valid_repository_path(&repo_link)) {
+ git_buf_swap(repo_path, &repo_link);
+
+ if (link_path)
+ error = git_buf_put(link_path, path.ptr, path.size);
+ }
+ break;
+ }
+ }
+
+ /* Move up one directory. If we're in_dot_git, we'll search the
+ * parent itself next. If we're !in_dot_git, we'll search .git
+ * in the parent directory next (added at the top of the loop). */
+ if (git_path_dirname_r(&path, path.ptr) < 0) {
+ error = -1;
+ break;
+ }
+
+ /* Once we've checked the directory (and .git if applicable),
+ * find the ceiling for a search. */
+ if (min_iterations && (--min_iterations == 0))
+ ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
+
+ /* Check if we should stop searching here. */
+ if (min_iterations == 0
+ && (path.ptr[ceiling_offset] == 0
+ || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH)))
+ break;
+ }
+
+ if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
+ if (!git_buf_len(repo_path))
+ git_buf_clear(parent_path);
+ else {
+ git_path_dirname_r(parent_path, path.ptr);
+ git_path_to_dir(parent_path);
+ }
+ if (git_buf_oom(parent_path))
+ return -1;
+ }
+
+ /* If we didn't find the repository, and we don't have any other error
+ * to report, report that. */
+ if (!git_buf_len(repo_path) && !error) {
+ giterr_set(GITERR_REPOSITORY,
+ "Could not find repository from '%s'", start_path);
+ error = GIT_ENOTFOUND;
+ }
+
+ git_buf_free(&path);
+ git_buf_free(&repo_link);
+ return error;
+}
+
+int git_repository_open_bare(
+ git_repository **repo_ptr,
+ const char *bare_path)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo = NULL;
+
+ if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0)
+ return error;
+
+ if (!valid_repository_path(&path)) {
+ git_buf_free(&path);
+ giterr_set(GITERR_REPOSITORY, "Path is not a repository: %s", bare_path);
+ return GIT_ENOTFOUND;
+ }
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ repo->path_repository = git_buf_detach(&path);
+ GITERR_CHECK_ALLOC(repo->path_repository);
+
+ /* of course we're bare! */
+ repo->is_bare = 1;
+ repo->workdir = NULL;
+
+ *repo_ptr = repo;
+ return 0;
+}
+
+static int _git_repository_open_ext_from_env(
+ git_repository **out,
+ const char *start_path)
+{
+ git_repository *repo = NULL;
+ git_index *index = NULL;
+ git_odb *odb = NULL;
+ git_buf dir_buf = GIT_BUF_INIT;
+ git_buf ceiling_dirs_buf = GIT_BUF_INIT;
+ git_buf across_fs_buf = GIT_BUF_INIT;
+ git_buf index_file_buf = GIT_BUF_INIT;
+ git_buf namespace_buf = GIT_BUF_INIT;
+ git_buf object_dir_buf = GIT_BUF_INIT;
+ git_buf alts_buf = GIT_BUF_INIT;
+ git_buf work_tree_buf = GIT_BUF_INIT;
+ git_buf common_dir_buf = GIT_BUF_INIT;
+ const char *ceiling_dirs = NULL;
+ unsigned flags = 0;
+ int error;
+
+ if (!start_path) {
+ error = git__getenv(&dir_buf, "GIT_DIR");
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ start_path = ".";
+ } else if (error < 0)
+ goto error;
+ else {
+ start_path = git_buf_cstr(&dir_buf);
+ flags |= GIT_REPOSITORY_OPEN_NO_SEARCH;
+ flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT;
+ }
+ }
+
+ error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else
+ ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
+
+ error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else {
+ int across_fs = 0;
+ error = git_config_parse_bool(&across_fs, git_buf_cstr(&across_fs_buf));
+ if (error < 0)
+ goto error;
+ if (across_fs)
+ flags |= GIT_REPOSITORY_OPEN_CROSS_FS;
+ }
+
+ error = git__getenv(&index_file_buf, "GIT_INDEX_FILE");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else {
+ error = git_index_open(&index, git_buf_cstr(&index_file_buf));
+ if (error < 0)
+ goto error;
+ }
+
+ error = git__getenv(&namespace_buf, "GIT_NAMESPACE");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+
+ error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else {
+ error = git_odb_open(&odb, git_buf_cstr(&object_dir_buf));
+ if (error < 0)
+ goto error;
+ }
+
+ error = git__getenv(&work_tree_buf, "GIT_WORK_TREE");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else {
+ giterr_set(GITERR_INVALID, "GIT_WORK_TREE unimplemented");
+ error = GIT_ERROR;
+ goto error;
+ }
+
+ error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR");
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0)
+ goto error;
+ else {
+ giterr_set(GITERR_INVALID, "GIT_COMMON_DIR unimplemented");
+ error = GIT_ERROR;
+ goto error;
+ }
+
+ error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs);
+ if (error < 0)
+ goto error;
+
+ if (odb)
+ git_repository_set_odb(repo, odb);
+
+ error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES");
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ } else if (error < 0)
+ goto error;
+ else {
+ const char *end;
+ char *alt, *sep;
+ if (!odb) {
+ error = git_repository_odb(&odb, repo);
+ if (error < 0)
+ goto error;
+ }
+
+ end = git_buf_cstr(&alts_buf) + git_buf_len(&alts_buf);
+ for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) {
+ for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++)
+ ;
+ if (*sep)
+ *sep = '\0';
+ error = git_odb_add_disk_alternate(odb, alt);
+ if (error < 0)
+ goto error;
+ }
+ }
+
+ if (git_buf_len(&namespace_buf)) {
+ error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf));
+ if (error < 0)
+ goto error;
+ }
+
+ git_repository_set_index(repo, index);
+
+ if (out) {
+ *out = repo;
+ goto success;
+ }
+error:
+ git_repository_free(repo);
+success:
+ git_odb_free(odb);
+ git_index_free(index);
+ git_buf_free(&common_dir_buf);
+ git_buf_free(&work_tree_buf);
+ git_buf_free(&alts_buf);
+ git_buf_free(&object_dir_buf);
+ git_buf_free(&namespace_buf);
+ git_buf_free(&index_file_buf);
+ git_buf_free(&across_fs_buf);
+ git_buf_free(&ceiling_dirs_buf);
+ git_buf_free(&dir_buf);
+ return error;
+}
+
+int git_repository_open_ext(
+ git_repository **repo_ptr,
+ const char *start_path,
+ unsigned int flags,
+ const char *ceiling_dirs)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
+ link_path = GIT_BUF_INIT;
+ git_repository *repo;
+ git_config *config = NULL;
+
+ if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
+ return _git_repository_open_ext_from_env(repo_ptr, start_path);
+
+ if (repo_ptr)
+ *repo_ptr = NULL;
+
+ error = find_repo(
+ &path, &parent, &link_path, start_path, flags, ceiling_dirs);
+
+ if (error < 0 || !repo_ptr)
+ return error;
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ repo->path_repository = git_buf_detach(&path);
+ GITERR_CHECK_ALLOC(repo->path_repository);
+
+ if (link_path.size) {
+ repo->path_gitlink = git_buf_detach(&link_path);
+ GITERR_CHECK_ALLOC(repo->path_gitlink);
+ }
+
+ /*
+ * We'd like to have the config, but git doesn't particularly
+ * care if it's not there, so we need to deal with that.
+ */
+
+ error = git_repository_config_snapshot(&config, repo);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if (config && (error = check_repositoryformatversion(config)) < 0)
+ goto cleanup;
+
+ if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
+ repo->is_bare = 1;
+ else {
+
+ if (config &&
+ ((error = load_config_data(repo, config)) < 0 ||
+ (error = load_workdir(repo, config, &parent)) < 0))
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&parent);
+ git_config_free(config);
+
+ if (error < 0)
+ git_repository_free(repo);
+ else
+ *repo_ptr = repo;
+
+ return error;
+}
+
+int git_repository_open(git_repository **repo_out, const char *path)
+{
+ return git_repository_open_ext(
+ repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);
+}
+
+int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb)
+{
+ git_repository *repo;
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ git_repository_set_odb(repo, odb);
+ *repo_out = repo;
+
+ return 0;
+}
+
+int git_repository_discover(
+ git_buf *out,
+ const char *start_path,
+ int across_fs,
+ const char *ceiling_dirs)
+{
+ uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0;
+
+ assert(start_path);
+
+ git_buf_sanitize(out);
+
+ return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs);
+}
+
+static int load_config(
+ git_config **out,
+ git_repository *repo,
+ const char *global_config_path,
+ const char *xdg_config_path,
+ const char *system_config_path,
+ const char *programdata_path)
+{
+ int error;
+ git_buf config_path = GIT_BUF_INIT;
+ git_config *cfg = NULL;
+
+ assert(repo && out);
+
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
+
+ error = git_buf_joinpath(
+ &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
+ if (error < 0)
+ goto on_error;
+
+ if ((error = git_config_add_file_ondisk(
+ cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ git_buf_free(&config_path);
+
+ if (global_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (xdg_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (system_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (programdata_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear(); /* clear any lingering ENOTFOUND errors */
+
+ *out = cfg;
+ return 0;
+
+on_error:
+ git_buf_free(&config_path);
+ git_config_free(cfg);
+ *out = NULL;
+ return error;
+}
+
+static const char *path_unless_empty(git_buf *buf)
+{
+ return git_buf_len(buf) > 0 ? git_buf_cstr(buf) : NULL;
+}
+
+int git_repository_config__weakptr(git_config **out, git_repository *repo)
+{
+ int error = 0;
+
+ if (repo->_config == NULL) {
+ git_buf global_buf = GIT_BUF_INIT;
+ git_buf xdg_buf = GIT_BUF_INIT;
+ git_buf system_buf = GIT_BUF_INIT;
+ git_buf programdata_buf = GIT_BUF_INIT;
+ git_config *config;
+
+ git_config_find_global(&global_buf);
+ git_config_find_xdg(&xdg_buf);
+ git_config_find_system(&system_buf);
+ git_config_find_programdata(&programdata_buf);
+
+ /* If there is no global file, open a backend for it anyway */
+ if (git_buf_len(&global_buf) == 0)
+ git_config__global_location(&global_buf);
+
+ error = load_config(
+ &config, repo,
+ path_unless_empty(&global_buf),
+ path_unless_empty(&xdg_buf),
+ path_unless_empty(&system_buf),
+ path_unless_empty(&programdata_buf));
+ if (!error) {
+ GIT_REFCOUNT_OWN(config, repo);
+
+ config = git__compare_and_swap(&repo->_config, NULL, config);
+ if (config != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
+ }
+ }
+
+ git_buf_free(&global_buf);
+ git_buf_free(&xdg_buf);
+ git_buf_free(&system_buf);
+ git_buf_free(&programdata_buf);
+ }
+
+ *out = repo->_config;
+ return error;
+}
+
+int git_repository_config(git_config **out, git_repository *repo)
+{
+ if (git_repository_config__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+int git_repository_config_snapshot(git_config **out, git_repository *repo)
+{
+ int error;
+ git_config *weak;
+
+ if ((error = git_repository_config__weakptr(&weak, repo)) < 0)
+ return error;
+
+ return git_config_snapshot(out, weak);
+}
+
+void git_repository_set_config(git_repository *repo, git_config *config)
+{
+ assert(repo && config);
+ set_config(repo, config);
+}
+
+int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
+{
+ int error = 0;
+
+ assert(repo && out);
+
+ if (repo->_odb == NULL) {
+ git_buf odb_path = GIT_BUF_INIT;
+ git_odb *odb;
+
+ if ((error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR)) < 0)
+ return error;
+
+ error = git_odb_open(&odb, odb_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(odb, repo);
+
+ odb = git__compare_and_swap(&repo->_odb, NULL, odb);
+ if (odb != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
+ }
+ }
+
+ git_buf_free(&odb_path);
+ }
+
+ *out = repo->_odb;
+ return error;
+}
+
+int git_repository_odb(git_odb **out, git_repository *repo)
+{
+ if (git_repository_odb__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+void git_repository_set_odb(git_repository *repo, git_odb *odb)
+{
+ assert(repo && odb);
+ set_odb(repo, odb);
+}
+
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
+{
+ int error = 0;
+
+ assert(out && repo);
+
+ if (repo->_refdb == NULL) {
+ git_refdb *refdb;
+
+ error = git_refdb_open(&refdb, repo);
+ if (!error) {
+ GIT_REFCOUNT_OWN(refdb, repo);
+
+ refdb = git__compare_and_swap(&repo->_refdb, NULL, refdb);
+ if (refdb != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
+ }
+ }
+ }
+
+ *out = repo->_refdb;
+ return error;
+}
+
+int git_repository_refdb(git_refdb **out, git_repository *repo)
+{
+ if (git_repository_refdb__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+void git_repository_set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ assert(repo && refdb);
+ set_refdb(repo, refdb);
+}
+
+int git_repository_index__weakptr(git_index **out, git_repository *repo)
+{
+ int error = 0;
+
+ assert(out && repo);
+
+ if (repo->_index == NULL) {
+ git_buf index_path = GIT_BUF_INIT;
+ git_index *index;
+
+ if ((error = git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE)) < 0)
+ return error;
+
+ error = git_index_open(&index, index_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(index, repo);
+
+ index = git__compare_and_swap(&repo->_index, NULL, index);
+ if (index != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
+ }
+
+ error = git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER);
+ }
+
+ git_buf_free(&index_path);
+ }
+
+ *out = repo->_index;
+ return error;
+}
+
+int git_repository_index(git_index **out, git_repository *repo)
+{
+ if (git_repository_index__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+void git_repository_set_index(git_repository *repo, git_index *index)
+{
+ assert(repo);
+ set_index(repo, index);
+}
+
+int git_repository_set_namespace(git_repository *repo, const char *namespace)
+{
+ git__free(repo->namespace);
+
+ if (namespace == NULL) {
+ repo->namespace = NULL;
+ return 0;
+ }
+
+ return (repo->namespace = git__strdup(namespace)) ? 0 : -1;
+}
+
+const char *git_repository_get_namespace(git_repository *repo)
+{
+ return repo->namespace;
+}
+
+#ifdef GIT_WIN32
+static int reserved_names_add8dot3(git_repository *repo, const char *path)
+{
+ char *name = git_win32_path_8dot3_name(path);
+ const char *def = GIT_DIR_SHORTNAME;
+ const char *def_dot_git = DOT_GIT;
+ size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME);
+ size_t def_dot_git_len = CONST_STRLEN(DOT_GIT);
+ git_buf *buf;
+
+ if (!name)
+ return 0;
+
+ name_len = strlen(name);
+
+ if ((name_len == def_len && memcmp(name, def, def_len) == 0) ||
+ (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) {
+ git__free(name);
+ return 0;
+ }
+
+ if ((buf = git_array_alloc(repo->reserved_names)) == NULL)
+ return -1;
+
+ git_buf_attach(buf, name, name_len);
+ return true;
+}
+
+bool git_repository__reserved_names(
+ git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs)
+{
+ GIT_UNUSED(include_ntfs);
+
+ if (repo->reserved_names.size == 0) {
+ git_buf *buf;
+ size_t i;
+
+ /* Add the static defaults */
+ for (i = 0; i < git_repository__reserved_names_win32_len; i++) {
+ if ((buf = git_array_alloc(repo->reserved_names)) == NULL)
+ goto on_error;
+
+ buf->ptr = git_repository__reserved_names_win32[i].ptr;
+ buf->size = git_repository__reserved_names_win32[i].size;
+ }
+
+ /* Try to add any repo-specific reserved names - the gitlink file
+ * within a submodule or the repository (if the repository directory
+ * is beneath the workdir). These are typically `.git`, but should
+ * be protected in case they are not. Note, repo and workdir paths
+ * are always prettified to end in `/`, so a prefixcmp is safe.
+ */
+ if (!repo->is_bare) {
+ int (*prefixcmp)(const char *, const char *);
+ int error, ignorecase;
+
+ error = git_repository__cvar(
+ &ignorecase, repo, GIT_CVAR_IGNORECASE);
+ prefixcmp = (error || ignorecase) ? git__prefixcmp_icase :
+ git__prefixcmp;
+
+ if (repo->path_gitlink &&
+ reserved_names_add8dot3(repo, repo->path_gitlink) < 0)
+ goto on_error;
+
+ if (repo->path_repository &&
+ prefixcmp(repo->path_repository, repo->workdir) == 0 &&
+ reserved_names_add8dot3(repo, repo->path_repository) < 0)
+ goto on_error;
+ }
+ }
+
+ *out = repo->reserved_names.ptr;
+ *outlen = repo->reserved_names.size;
+
+ return true;
+
+ /* Always give good defaults, even on OOM */
+on_error:
+ *out = git_repository__reserved_names_win32;
+ *outlen = git_repository__reserved_names_win32_len;
+
+ return false;
+}
+#else
+bool git_repository__reserved_names(
+ git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs)
+{
+ GIT_UNUSED(repo);
+
+ if (include_ntfs) {
+ *out = git_repository__reserved_names_win32;
+ *outlen = git_repository__reserved_names_win32_len;
+ } else {
+ *out = git_repository__reserved_names_posix;
+ *outlen = git_repository__reserved_names_posix_len;
+ }
+
+ return true;
+}
+#endif
+
+static int check_repositoryformatversion(git_config *config)
+{
+ int version, error;
+
+ error = git_config_get_int32(&version, config, "core.repositoryformatversion");
+ /* git ignores this if the config variable isn't there */
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return -1;
+
+ if (GIT_REPO_VERSION < version) {
+ giterr_set(GITERR_REPOSITORY,
+ "Unsupported repository version %d. Only versions up to %d are supported.",
+ version, GIT_REPO_VERSION);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int repo_init_create_head(const char *git_dir, const char *ref_name)
+{
+ git_buf ref_path = GIT_BUF_INIT;
+ git_filebuf ref = GIT_FILEBUF_INIT;
+ const char *fmt;
+
+ if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||
+ git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE) < 0)
+ goto fail;
+
+ if (!ref_name)
+ ref_name = GIT_BRANCH_MASTER;
+
+ if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)
+ fmt = "ref: %s\n";
+ else
+ fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";
+
+ if (git_filebuf_printf(&ref, fmt, ref_name) < 0 ||
+ git_filebuf_commit(&ref) < 0)
+ goto fail;
+
+ git_buf_free(&ref_path);
+ return 0;
+
+fail:
+ git_buf_free(&ref_path);
+ git_filebuf_cleanup(&ref);
+ return -1;
+}
+
+static bool is_chmod_supported(const char *file_path)
+{
+ struct stat st1, st2;
+
+ if (p_stat(file_path, &st1) < 0)
+ return false;
+
+ if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0)
+ return false;
+
+ if (p_stat(file_path, &st2) < 0)
+ return false;
+
+ return (st1.st_mode != st2.st_mode);
+}
+
+static bool is_filesystem_case_insensitive(const char *gitdir_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int is_insensitive = -1;
+
+ if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg"))
+ is_insensitive = git_path_exists(git_buf_cstr(&path));
+
+ git_buf_free(&path);
+ return is_insensitive;
+}
+
+static bool are_symlinks_supported(const char *wd_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ struct stat st;
+ int symlinks_supported = -1;
+
+ if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 ||
+ p_close(fd) < 0 ||
+ p_unlink(path.ptr) < 0 ||
+ p_symlink("testing", path.ptr) < 0 ||
+ p_lstat(path.ptr, &st) < 0)
+ symlinks_supported = false;
+ else
+ symlinks_supported = (S_ISLNK(st.st_mode) != 0);
+
+ (void)p_unlink(path.ptr);
+ git_buf_free(&path);
+
+ return symlinks_supported;
+}
+
+static int create_empty_file(const char *path, mode_t mode)
+{
+ int fd;
+
+ if ((fd = p_creat(path, mode)) < 0) {
+ giterr_set(GITERR_OS, "Error while creating '%s'", path);
+ return -1;
+ }
+
+ if (p_close(fd) < 0) {
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int repo_local_config(
+ git_config **out,
+ git_buf *config_dir,
+ git_repository *repo,
+ const char *repo_dir)
+{
+ int error = 0;
+ git_config *parent;
+ const char *cfg_path;
+
+ if (git_buf_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
+ return -1;
+ cfg_path = git_buf_cstr(config_dir);
+
+ /* make LOCAL config if missing */
+ if (!git_path_isfile(cfg_path) &&
+ (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0)
+ return error;
+
+ /* if no repo, just open that file directly */
+ if (!repo)
+ return git_config_open_ondisk(out, cfg_path);
+
+ /* otherwise, open parent config and get that level */
+ if ((error = git_repository_config__weakptr(&parent, repo)) < 0)
+ return error;
+
+ if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ giterr_clear();
+
+ if (!(error = git_config_add_file_ondisk(
+ parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false)))
+ error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL);
+ }
+
+ git_config_free(parent);
+
+ return error;
+}
+
+static int repo_init_fs_configs(
+ git_config *cfg,
+ const char *cfg_path,
+ const char *repo_dir,
+ const char *work_dir,
+ bool update_ignorecase)
+{
+ int error = 0;
+
+ if (!work_dir)
+ work_dir = repo_dir;
+
+ if ((error = git_config_set_bool(
+ cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0)
+ return error;
+
+ if (!are_symlinks_supported(work_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.symlinks") < 0)
+ giterr_clear();
+
+ if (update_ignorecase) {
+ if (is_filesystem_case_insensitive(repo_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0)
+ giterr_clear();
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_config_set_bool(
+ cfg, "core.precomposeunicode",
+ git_path_does_fs_decompose_unicode(work_dir))) < 0)
+ return error;
+ /* on non-iconv platforms, don't even set core.precomposeunicode */
+#endif
+
+ return 0;
+}
+
+static int repo_init_config(
+ const char *repo_dir,
+ const char *work_dir,
+ uint32_t flags,
+ uint32_t mode)
+{
+ int error = 0;
+ git_buf cfg_path = GIT_BUF_INIT, worktree_path = GIT_BUF_INIT;
+ git_config *config = NULL;
+ bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0);
+ bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0);
+
+ if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0)
+ goto cleanup;
+
+ if (is_reinit && (error = check_repositoryformatversion(config)) < 0)
+ goto cleanup;
+
+#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \
+ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
+ goto cleanup; } while (0)
+
+ SET_REPO_CONFIG(bool, "core.bare", is_bare);
+ SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);
+
+ if ((error = repo_init_fs_configs(
+ config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0)
+ goto cleanup;
+
+ if (!is_bare) {
+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
+
+ if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
+ if ((error = git_buf_sets(&worktree_path, work_dir)) < 0)
+ goto cleanup;
+
+ if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK))
+ if ((error = git_path_make_relative(&worktree_path, repo_dir)) < 0)
+ goto cleanup;
+
+ SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr);
+ } else if (is_reinit) {
+ if (git_config_delete_entry(config, "core.worktree") < 0)
+ giterr_clear();
+ }
+ }
+
+ if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 1);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
+ }
+ else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 2);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
+ }
+
+cleanup:
+ git_buf_free(&cfg_path);
+ git_buf_free(&worktree_path);
+ git_config_free(config);
+
+ return error;
+}
+
+static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p)
+{
+ git_repository *smrepo = NULL;
+ GIT_UNUSED(n); GIT_UNUSED(p);
+
+ if (git_submodule_open(&smrepo, sm) < 0 ||
+ git_repository_reinit_filesystem(smrepo, true) < 0)
+ giterr_clear();
+ git_repository_free(smrepo);
+
+ return 0;
+}
+
+int git_repository_reinit_filesystem(git_repository *repo, int recurse)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ git_config *config = NULL;
+ const char *repo_dir = git_repository_path(repo);
+
+ if (!(error = repo_local_config(&config, &path, repo, repo_dir)))
+ error = repo_init_fs_configs(
+ config, path.ptr, repo_dir, git_repository_workdir(repo), true);
+
+ git_config_free(config);
+ git_buf_free(&path);
+
+ git_repository__cvar_cache_clear(repo);
+
+ if (!repo->is_bare && recurse)
+ (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL);
+
+ return error;
+}
+
+static int repo_write_template(
+ const char *git_dir,
+ bool allow_overwrite,
+ const char *file,
+ mode_t mode,
+ bool hidden,
+ const char *content)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd, error = 0, flags;
+
+ if (git_buf_joinpath(&path, git_dir, file) < 0)
+ return -1;
+
+ if (allow_overwrite)
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ else
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+
+ fd = p_open(git_buf_cstr(&path), flags, mode);
+
+ if (fd >= 0) {
+ error = p_write(fd, content, strlen(content));
+
+ p_close(fd);
+ }
+ else if (errno != EEXIST)
+ error = fd;
+
+#ifdef GIT_WIN32
+ if (!error && hidden) {
+ if (git_win32__set_hidden(path.ptr, true) < 0)
+ error = -1;
+ }
+#else
+ GIT_UNUSED(hidden);
+#endif
+
+ git_buf_free(&path);
+
+ if (error)
+ giterr_set(GITERR_OS,
+ "Failed to initialize repository with template '%s'", file);
+
+ return error;
+}
+
+static int repo_write_gitlink(
+ const char *in_dir, const char *to_repo, bool use_relative_path)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ git_buf path_to_repo = GIT_BUF_INIT;
+ struct stat st;
+
+ git_path_dirname_r(&buf, to_repo);
+ git_path_to_dir(&buf);
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* don't write gitlink to natural workdir */
+ if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 &&
+ strcmp(in_dir, buf.ptr) == 0)
+ {
+ error = GIT_PASSTHROUGH;
+ goto cleanup;
+ }
+
+ if ((error = git_buf_joinpath(&buf, in_dir, DOT_GIT)) < 0)
+ goto cleanup;
+
+ if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) {
+ giterr_set(GITERR_REPOSITORY,
+ "Cannot overwrite gitlink file into path '%s'", in_dir);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
+
+ git_buf_clear(&buf);
+
+ error = git_buf_sets(&path_to_repo, to_repo);
+
+ if (!error && use_relative_path)
+ error = git_path_make_relative(&path_to_repo, in_dir);
+
+ if (!error)
+ error = git_buf_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr);
+
+ if (!error)
+ error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr);
+
+cleanup:
+ git_buf_free(&buf);
+ git_buf_free(&path_to_repo);
+ return error;
+}
+
+static mode_t pick_dir_mode(git_repository_init_options *opts)
+{
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
+ return 0777;
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
+ return (0775 | S_ISGID);
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL)
+ return (0777 | S_ISGID);
+ return opts->mode;
+}
+
+#include "repo_template.h"
+
+static int repo_init_structure(
+ const char *repo_dir,
+ const char *work_dir,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ repo_template_item *tpl;
+ bool external_tpl =
+ ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0);
+ mode_t dmode = pick_dir_mode(opts);
+ bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK;
+
+ /* Hide the ".git" directory */
+#ifdef GIT_WIN32
+ if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {
+ if (git_win32__set_hidden(repo_dir, true) < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to mark Git repository folder as hidden");
+ return -1;
+ }
+ }
+#endif
+
+ /* Create the .git gitlink if appropriate */
+ if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 &&
+ (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0)
+ {
+ if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0)
+ return -1;
+ }
+
+ /* Copy external template if requested */
+ if (external_tpl) {
+ git_config *cfg = NULL;
+ const char *tdir = NULL;
+ bool default_template = false;
+ git_buf template_buf = GIT_BUF_INIT;
+
+ if (opts->template_path)
+ tdir = opts->template_path;
+ else if ((error = git_config_open_default(&cfg)) >= 0) {
+ if (!git_config_get_path(&template_buf, cfg, "init.templatedir"))
+ tdir = template_buf.ptr;
+ giterr_clear();
+ }
+
+ if (!tdir) {
+ if (!(error = git_sysdir_find_template_dir(&template_buf)))
+ tdir = template_buf.ptr;
+ default_template = true;
+ }
+
+ if (tdir) {
+ uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS |
+ GIT_CPDIR_SIMPLE_TO_MODE |
+ GIT_CPDIR_COPY_DOTFILES;
+ if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK)
+ cpflags |= GIT_CPDIR_CHMOD_DIRS;
+ error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode);
+ }
+
+ git_buf_free(&template_buf);
+ git_config_free(cfg);
+
+ if (error < 0) {
+ if (!default_template)
+ return error;
+
+ /* if template was default, ignore error and use internal */
+ giterr_clear();
+ external_tpl = false;
+ error = 0;
+ }
+ }
+
+ /* Copy internal template
+ * - always ensure existence of dirs
+ * - only create files if no external template was specified
+ */
+ for (tpl = repo_template; !error && tpl->path; ++tpl) {
+ if (!tpl->content) {
+ uint32_t mkdir_flags = GIT_MKDIR_PATH;
+ if (chmod)
+ mkdir_flags |= GIT_MKDIR_CHMOD;
+
+ error = git_futils_mkdir_relative(
+ tpl->path, repo_dir, dmode, mkdir_flags, NULL);
+ }
+ else if (!external_tpl) {
+ const char *content = tpl->content;
+
+ if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0)
+ content = opts->description;
+
+ error = repo_write_template(
+ repo_dir, false, tpl->path, tpl->mode, false, content);
+ }
+ }
+
+ return error;
+}
+
+static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2)
+{
+ /* When making parent directories during repository initialization
+ * don't try to set gid or grant world write access
+ */
+ return git_futils_mkdir(
+ buf->ptr, mode & ~(S_ISGID | 0002),
+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR |
+ (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST));
+}
+
+static int repo_init_directories(
+ git_buf *repo_path,
+ git_buf *wd_path,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ bool is_bare, add_dotgit, has_dotgit, natural_wd;
+ mode_t dirmode;
+
+ /* There are three possible rules for what we are allowed to create:
+ * - MKPATH means anything we need
+ * - MKDIR means just the .git directory and its parent and the workdir
+ * - Neither means only the .git directory can be created
+ *
+ * There are 5 "segments" of path that we might need to deal with:
+ * 1. The .git directory
+ * 2. The parent of the .git directory
+ * 3. Everything above the parent of the .git directory
+ * 4. The working directory (often the same as #2)
+ * 5. Everything above the working directory (often the same as #3)
+ *
+ * For all directories created, we start with the init_mode value for
+ * permissions and then strip off bits in some cases:
+ *
+ * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH
+ * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID
+ * For all rules, we create #1 using the untouched init_mode
+ */
+
+ /* set up repo path */
+
+ is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
+
+ add_dotgit =
+ (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 &&
+ !is_bare &&
+ git__suffixcmp(given_repo, "/" DOT_GIT) != 0 &&
+ git__suffixcmp(given_repo, "/" GIT_DIR) != 0;
+
+ if (git_buf_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0)
+ return -1;
+
+ has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0);
+ if (has_dotgit)
+ opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT;
+
+ /* set up workdir path */
+
+ if (!is_bare) {
+ if (opts->workdir_path) {
+ if (git_path_join_unrooted(
+ wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0)
+ return -1;
+ } else if (has_dotgit) {
+ if (git_path_dirname_r(wd_path, repo_path->ptr) < 0)
+ return -1;
+ } else {
+ giterr_set(GITERR_REPOSITORY, "Cannot pick working directory"
+ " for non-bare repository that isn't a '.git' directory");
+ return -1;
+ }
+
+ if (git_path_to_dir(wd_path) < 0)
+ return -1;
+ } else {
+ git_buf_clear(wd_path);
+ }
+
+ natural_wd =
+ has_dotgit &&
+ wd_path->size > 0 &&
+ wd_path->size + strlen(GIT_DIR) == repo_path->size &&
+ memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0;
+ if (natural_wd)
+ opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD;
+
+ /* create directories as needed / requested */
+
+ dirmode = pick_dir_mode(opts);
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) {
+ /* create path #5 */
+ if (wd_path->size > 0 &&
+ (error = mkdir_parent(wd_path, dirmode, false)) < 0)
+ return error;
+
+ /* create path #3 (if not the same as #5) */
+ if (!natural_wd &&
+ (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0)
+ return error;
+ }
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)
+ {
+ /* create path #4 */
+ if (wd_path->size > 0 &&
+ (error = git_futils_mkdir(
+ wd_path->ptr, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR)) < 0)
+ return error;
+
+ /* create path #2 (if not the same as #4) */
+ if (!natural_wd &&
+ (error = git_futils_mkdir(
+ repo_path->ptr, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0)
+ return error;
+ }
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 ||
+ has_dotgit)
+ {
+ /* create path #1 */
+ error = git_futils_mkdir(repo_path->ptr, dirmode,
+ GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0));
+ }
+
+ /* prettify both directories now that they are created */
+
+ if (!error) {
+ error = git_path_prettify_dir(repo_path, repo_path->ptr, NULL);
+
+ if (!error && wd_path->size > 0)
+ error = git_path_prettify_dir(wd_path, wd_path->ptr, NULL);
+ }
+
+ return error;
+}
+
+static int repo_init_create_origin(git_repository *repo, const char *url)
+{
+ int error;
+ git_remote *remote;
+
+ if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) {
+ git_remote_free(remote);
+ }
+
+ return error;
+}
+
+int git_repository_init(
+ git_repository **repo_out, const char *path, unsigned is_bare)
+{
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */
+ if (is_bare)
+ opts.flags |= GIT_REPOSITORY_INIT_BARE;
+
+ return git_repository_init_ext(repo_out, path, &opts);
+}
+
+int git_repository_init_ext(
+ git_repository **out,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error;
+ git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;
+ const char *wd;
+
+ assert(out && given_repo && opts);
+
+ GITERR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options");
+
+ error = repo_init_directories(&repo_path, &wd_path, given_repo, opts);
+ if (error < 0)
+ goto cleanup;
+
+ wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path);
+ if (valid_repository_path(&repo_path)) {
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {
+ giterr_set(GITERR_REPOSITORY,
+ "Attempt to reinitialize '%s'", given_repo);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
+
+ opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
+
+ error = repo_init_config(
+ repo_path.ptr, wd, opts->flags, opts->mode);
+
+ /* TODO: reinitialize the templates */
+ }
+ else {
+ if (!(error = repo_init_structure(
+ repo_path.ptr, wd, opts)) &&
+ !(error = repo_init_config(
+ repo_path.ptr, wd, opts->flags, opts->mode)))
+ error = repo_init_create_head(
+ repo_path.ptr, opts->initial_head);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ error = git_repository_open(out, repo_path.ptr);
+
+ if (!error && opts->origin_url)
+ error = repo_init_create_origin(*out, opts->origin_url);
+
+cleanup:
+ git_buf_free(&repo_path);
+ git_buf_free(&wd_path);
+
+ return error;
+}
+
+int git_repository_head_detached(git_repository *repo)
+{
+ git_reference *ref;
+ git_odb *odb = NULL;
+ int exists;
+
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ return -1;
+
+ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
+ return -1;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ git_reference_free(ref);
+ return 0;
+ }
+
+ exists = git_odb_exists(odb, git_reference_target(ref));
+
+ git_reference_free(ref);
+ return exists;
+}
+
+int git_repository_head(git_reference **head_out, git_repository *repo)
+{
+ git_reference *head;
+ int error;
+
+ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if (git_reference_type(head) == GIT_REF_OID) {
+ *head_out = head;
+ return 0;
+ }
+
+ error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1);
+ git_reference_free(head);
+
+ return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error;
+}
+
+int git_repository_head_unborn(git_repository *repo)
+{
+ git_reference *ref = NULL;
+ int error;
+
+ error = git_repository_head(&ref, repo);
+ git_reference_free(ref);
+
+ if (error == GIT_EUNBORNBRANCH) {
+ giterr_clear();
+ return 1;
+ }
+
+ if (error < 0)
+ return -1;
+
+ return 0;
+}
+
+static int at_least_one_cb(const char *refname, void *payload)
+{
+ GIT_UNUSED(refname);
+ GIT_UNUSED(payload);
+ return GIT_PASSTHROUGH;
+}
+
+static int repo_contains_no_reference(git_repository *repo)
+{
+ int error = git_reference_foreach_name(repo, &at_least_one_cb, NULL);
+
+ if (error == GIT_PASSTHROUGH)
+ return 0;
+
+ if (!error)
+ return 1;
+
+ return error;
+}
+
+int git_repository_is_empty(git_repository *repo)
+{
+ git_reference *head = NULL;
+ int is_empty = 0;
+
+ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
+ return -1;
+
+ if (git_reference_type(head) == GIT_REF_SYMBOLIC)
+ is_empty =
+ (strcmp(git_reference_symbolic_target(head),
+ GIT_REFS_HEADS_DIR "master") == 0) &&
+ repo_contains_no_reference(repo);
+
+ git_reference_free(head);
+
+ return is_empty;
+}
+
+const char *git_repository_path(git_repository *repo)
+{
+ assert(repo);
+ return repo->path_repository;
+}
+
+const char *git_repository_workdir(git_repository *repo)
+{
+ assert(repo);
+
+ if (repo->is_bare)
+ return NULL;
+
+ return repo->workdir;
+}
+
+int git_repository_set_workdir(
+ git_repository *repo, const char *workdir, int update_gitlink)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(repo && workdir);
+
+ if (git_path_prettify_dir(&path, workdir, NULL) < 0)
+ return -1;
+
+ if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0)
+ return 0;
+
+ if (update_gitlink) {
+ git_config *config;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ return -1;
+
+ error = repo_write_gitlink(path.ptr, git_repository_path(repo), false);
+
+ /* passthrough error means gitlink is unnecessary */
+ if (error == GIT_PASSTHROUGH)
+ error = git_config_delete_entry(config, "core.worktree");
+ else if (!error)
+ error = git_config_set_string(config, "core.worktree", path.ptr);
+
+ if (!error)
+ error = git_config_set_bool(config, "core.bare", false);
+ }
+
+ if (!error) {
+ char *old_workdir = repo->workdir;
+
+ repo->workdir = git_buf_detach(&path);
+ repo->is_bare = 0;
+
+ git__free(old_workdir);
+ }
+
+ return error;
+}
+
+int git_repository_is_bare(git_repository *repo)
+{
+ assert(repo);
+ return repo->is_bare;
+}
+
+int git_repository_set_bare(git_repository *repo)
+{
+ int error;
+ git_config *config;
+
+ assert(repo);
+
+ if (repo->is_bare)
+ return 0;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ if ((error = git_config_set_bool(config, "core.bare", true)) < 0)
+ return error;
+
+ if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0)
+ return error;
+
+ git__free(repo->workdir);
+ repo->workdir = NULL;
+ repo->is_bare = 1;
+
+ return 0;
+}
+
+int git_repository_head_tree(git_tree **tree, git_repository *repo)
+{
+ git_reference *head;
+ git_object *obj;
+ int error;
+
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ if ((error = git_reference_peel(&obj, head, GIT_OBJ_TREE)) < 0)
+ goto cleanup;
+
+ *tree = (git_tree *)obj;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char orig_head_str[GIT_OID_HEXSZ];
+ int error = 0;
+
+ git_oid_fmt(orig_head_str, orig_head);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 &&
+ (error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+int git_repository_message(git_buf *out, git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ git_buf_sanitize(out);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
+ if (errno == ENOENT)
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_OS, "Could not access message file");
+ } else {
+ error = git_futils_readbuffer(out, git_buf_cstr(&path));
+ }
+
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_repository_message_remove(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ error = p_unlink(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_repository_hashfile(
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path)
+{
+ int error;
+ git_filter_list *fl = NULL;
+ git_file fd = -1;
+ git_off_t len;
+ git_buf full_path = GIT_BUF_INIT;
+
+ assert(out && path && repo); /* as_path can be NULL */
+
+ /* At some point, it would be nice if repo could be NULL to just
+ * apply filter rules defined in system and global files, but for
+ * now that is not possible because git_filters_load() needs it.
+ */
+
+ error = git_path_join_unrooted(
+ &full_path, path, git_repository_workdir(repo), NULL);
+ if (error < 0)
+ return error;
+
+ if (!as_path)
+ as_path = path;
+
+ /* passing empty string for "as_path" indicated --no-filters */
+ if (strlen(as_path) > 0) {
+ error = git_filter_list_load(
+ &fl, repo, NULL, as_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT);
+ if (error < 0)
+ return error;
+ } else {
+ error = 0;
+ }
+
+ /* at this point, error is a count of the number of loaded filters */
+
+ fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0) {
+ error = fd;
+ goto cleanup;
+ }
+
+ len = git_futils_filesize(fd);
+ if (len < 0) {
+ error = (int)len;
+ goto cleanup;
+ }
+
+ if (!git__is_sizet(len)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl);
+
+cleanup:
+ if (fd >= 0)
+ p_close(fd);
+ git_filter_list_free(fl);
+ git_buf_free(&full_path);
+
+ return error;
+}
+
+static int checkout_message(git_buf *out, git_reference *old, const char *new)
+{
+ git_buf_puts(out, "checkout: moving from ");
+
+ if (git_reference_type(old) == GIT_REF_SYMBOLIC)
+ git_buf_puts(out, git_reference__shorthand(git_reference_symbolic_target(old)));
+ else
+ git_buf_puts(out, git_oid_tostr_s(git_reference_target(old)));
+
+ git_buf_puts(out, " to ");
+
+ if (git_reference__is_branch(new))
+ git_buf_puts(out, git_reference__shorthand(new));
+ else
+ git_buf_puts(out, new);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ return 0;
+}
+
+int git_repository_set_head(
+ git_repository* repo,
+ const char* refname)
+{
+ git_reference *ref = NULL, *current = NULL, *new_head = NULL;
+ git_buf log_message = GIT_BUF_INIT;
+ int error;
+
+ assert(repo && refname);
+
+ if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = checkout_message(&log_message, current, refname)) < 0)
+ goto cleanup;
+
+ error = git_reference_lookup(&ref, repo, refname);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if (!error) {
+ if (git_reference_is_branch(ref)) {
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE,
+ git_reference_name(ref), true, git_buf_cstr(&log_message));
+ } else {
+ error = git_repository_set_head_detached(repo, git_reference_target(ref));
+ }
+ } else if (git_reference__is_branch(refname)) {
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname,
+ true, git_buf_cstr(&log_message));
+ }
+
+cleanup:
+ git_buf_free(&log_message);
+ git_reference_free(current);
+ git_reference_free(ref);
+ git_reference_free(new_head);
+ return error;
+}
+
+static int detach(git_repository *repo, const git_oid *id, const char *from)
+{
+ int error;
+ git_buf log_message = GIT_BUF_INIT;
+ git_object *object = NULL, *peeled = NULL;
+ git_reference *new_head = NULL, *current = NULL;
+
+ assert(repo && id);
+
+ if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ if (from == NULL)
+ from = git_oid_tostr_s(git_object_id(peeled));
+
+ if ((error = checkout_message(&log_message, current, from)) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message));
+
+cleanup:
+ git_buf_free(&log_message);
+ git_object_free(object);
+ git_object_free(peeled);
+ git_reference_free(current);
+ git_reference_free(new_head);
+ return error;
+}
+
+int git_repository_set_head_detached(
+ git_repository* repo,
+ const git_oid* commitish)
+{
+ return detach(repo, commitish, NULL);
+}
+
+int git_repository_set_head_detached_from_annotated(
+ git_repository *repo,
+ const git_annotated_commit *commitish)
+{
+ assert(repo && commitish);
+
+ return detach(repo, git_annotated_commit_id(commitish), commitish->description);
+}
+
+int git_repository_detach_head(git_repository* repo)
+{
+ git_reference *old_head = NULL, *new_head = NULL, *current = NULL;
+ git_object *object = NULL;
+ git_buf log_message = GIT_BUF_INIT;
+ int error;
+
+ assert(repo);
+
+ if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_repository_head(&old_head, repo)) < 0)
+ goto cleanup;
+
+ if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(object)))) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head),
+ 1, git_buf_cstr(&log_message));
+
+cleanup:
+ git_buf_free(&log_message);
+ git_object_free(object);
+ git_reference_free(old_head);
+ git_reference_free(new_head);
+ git_reference_free(current);
+ return error;
+}
+
+/**
+ * Loosely ported from git.git
+ * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289
+ */
+int git_repository_state(git_repository *repo)
+{
+ git_buf repo_path = GIT_BUF_INIT;
+ int state = GIT_REPOSITORY_STATE_NONE;
+
+ assert(repo);
+
+ if (git_buf_puts(&repo_path, repo->path_repository) < 0)
+ return -1;
+
+ if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR))
+ state = GIT_REPOSITORY_STATE_REBASE_MERGE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_MERGE;
+ else if (git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) {
+ state = GIT_REPOSITORY_STATE_REVERT;
+ if (git_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) {
+ state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE;
+ }
+ } else if (git_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) {
+ state = GIT_REPOSITORY_STATE_CHERRYPICK;
+ if (git_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) {
+ state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE;
+ }
+ } else if (git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))
+ state = GIT_REPOSITORY_STATE_BISECT;
+
+ git_buf_free(&repo_path);
+ return state;
+}
+
+int git_repository__cleanup_files(
+ git_repository *repo, const char *files[], size_t files_len)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t i;
+ int error;
+
+ for (error = 0, i = 0; !error && i < files_len; ++i) {
+ const char *path;
+
+ if (git_buf_joinpath(&buf, repo->path_repository, files[i]) < 0)
+ return -1;
+
+ path = git_buf_cstr(&buf);
+
+ if (git_path_isfile(path)) {
+ error = p_unlink(path);
+ } else if (git_path_isdir(path)) {
+ error = git_futils_rmdir_r(path, NULL,
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
+ }
+
+ git_buf_clear(&buf);
+ }
+
+ git_buf_free(&buf);
+ return error;
+}
+
+static const char *state_files[] = {
+ GIT_MERGE_HEAD_FILE,
+ GIT_MERGE_MODE_FILE,
+ GIT_MERGE_MSG_FILE,
+ GIT_REVERT_HEAD_FILE,
+ GIT_CHERRYPICK_HEAD_FILE,
+ GIT_BISECT_LOG_FILE,
+ GIT_REBASE_MERGE_DIR,
+ GIT_REBASE_APPLY_DIR,
+ GIT_SEQUENCER_DIR,
+};
+
+int git_repository_state_cleanup(git_repository *repo)
+{
+ assert(repo);
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+int git_repository_is_shallow(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ if ((error = git_buf_joinpath(&path, repo->path_repository, "shallow")) < 0)
+ return error;
+
+ error = git_path_lstat(path.ptr, &st);
+ git_buf_free(&path);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+
+ if (error < 0)
+ return error;
+ return st.st_size == 0 ? 0 : 1;
+}
+
+int git_repository_init_init_options(
+ git_repository_init_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_repository_init_options,
+ GIT_REPOSITORY_INIT_OPTIONS_INIT);
+ return 0;
+}
+
+int git_repository_ident(const char **name, const char **email, const git_repository *repo)
+{
+ *name = repo->ident_name;
+ *email = repo->ident_email;
+
+ return 0;
+}
+
+int git_repository_set_ident(git_repository *repo, const char *name, const char *email)
+{
+ char *tmp_name = NULL, *tmp_email = NULL;
+
+ if (name) {
+ tmp_name = git__strdup(name);
+ GITERR_CHECK_ALLOC(tmp_name);
+ }
+
+ if (email) {
+ tmp_email = git__strdup(email);
+ GITERR_CHECK_ALLOC(tmp_email);
+ }
+
+ tmp_name = git__swap(repo->ident_name, tmp_name);
+ tmp_email = git__swap(repo->ident_email, tmp_email);
+
+ git__free(tmp_name);
+ git__free(tmp_email);
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_repository_h__
+#define INCLUDE_repository_h__
+
+#include "git2/common.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/config.h"
+
+#include "array.h"
+#include "cache.h"
+#include "refs.h"
+#include "buffer.h"
+#include "object.h"
+#include "attrcache.h"
+#include "submodule.h"
+#include "diff_driver.h"
+
+#define DOT_GIT ".git"
+#define GIT_DIR DOT_GIT "/"
+#define GIT_DIR_MODE 0755
+#define GIT_BARE_DIR_MODE 0777
+
+/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */
+#define GIT_DIR_SHORTNAME "GIT~1"
+
+/** Cvar cache identifiers */
+typedef enum {
+ GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */
+ GIT_CVAR_EOL, /* core.eol */
+ GIT_CVAR_SYMLINKS, /* core.symlinks */
+ GIT_CVAR_IGNORECASE, /* core.ignorecase */
+ GIT_CVAR_FILEMODE, /* core.filemode */
+ GIT_CVAR_IGNORESTAT, /* core.ignorestat */
+ GIT_CVAR_TRUSTCTIME, /* core.trustctime */
+ GIT_CVAR_ABBREV, /* core.abbrev */
+ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */
+ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */
+ GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */
+ GIT_CVAR_PROTECTHFS, /* core.protectHFS */
+ GIT_CVAR_PROTECTNTFS, /* core.protectNTFS */
+ GIT_CVAR_CACHE_MAX
+} git_cvar_cached;
+
+/**
+ * CVAR value enumerations
+ *
+ * These are the values that are actually stored in the cvar cache, instead
+ * of their string equivalents. These values are internal and symbolic;
+ * make sure that none of them is set to `-1`, since that is the unique
+ * identifier for "not cached"
+ */
+typedef enum {
+ /* The value hasn't been loaded from the cache yet */
+ GIT_CVAR_NOT_CACHED = -1,
+
+ /* core.safecrlf: false, 'fail', 'warn' */
+ GIT_SAFE_CRLF_FALSE = 0,
+ GIT_SAFE_CRLF_FAIL = 1,
+ GIT_SAFE_CRLF_WARN = 2,
+
+ /* core.autocrlf: false, true, 'input; */
+ GIT_AUTO_CRLF_FALSE = 0,
+ GIT_AUTO_CRLF_TRUE = 1,
+ GIT_AUTO_CRLF_INPUT = 2,
+ GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE,
+
+ /* core.eol: unset, 'crlf', 'lf', 'native' */
+ GIT_EOL_UNSET = 0,
+ GIT_EOL_CRLF = 1,
+ GIT_EOL_LF = 2,
+#ifdef GIT_WIN32
+ GIT_EOL_NATIVE = GIT_EOL_CRLF,
+#else
+ GIT_EOL_NATIVE = GIT_EOL_LF,
+#endif
+ GIT_EOL_DEFAULT = GIT_EOL_NATIVE,
+
+ /* core.symlinks: bool */
+ GIT_SYMLINKS_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorecase */
+ GIT_IGNORECASE_DEFAULT = GIT_CVAR_FALSE,
+ /* core.filemode */
+ GIT_FILEMODE_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorestat */
+ GIT_IGNORESTAT_DEFAULT = GIT_CVAR_FALSE,
+ /* core.trustctime */
+ GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE,
+ /* core.abbrev */
+ GIT_ABBREV_DEFAULT = 7,
+ /* core.precomposeunicode */
+ GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE,
+ /* core.safecrlf */
+ GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE,
+ /* core.logallrefupdates */
+ GIT_LOGALLREFUPDATES_UNSET = 2,
+ GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET,
+ /* core.protectHFS */
+ GIT_PROTECTHFS_DEFAULT = GIT_CVAR_FALSE,
+ /* core.protectNTFS */
+ GIT_PROTECTNTFS_DEFAULT = GIT_CVAR_FALSE,
+} git_cvar_value;
+
+/* internal repository init flags */
+enum {
+ GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16),
+ GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17),
+ GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18),
+};
+
+/** Internal structure for repository object */
+struct git_repository {
+ git_odb *_odb;
+ git_refdb *_refdb;
+ git_config *_config;
+ git_index *_index;
+
+ git_cache objects;
+ git_attr_cache *attrcache;
+ git_diff_driver_registry *diff_drivers;
+
+ char *path_repository;
+ char *path_gitlink;
+ char *workdir;
+ char *namespace;
+
+ char *ident_name;
+ char *ident_email;
+
+ git_array_t(git_buf) reserved_names;
+
+ unsigned is_bare:1;
+
+ unsigned int lru_counter;
+
+ git_atomic attr_session_key;
+
+ git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
+};
+
+GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
+{
+ return repo->attrcache;
+}
+
+int git_repository_head_tree(git_tree **tree, git_repository *repo);
+
+/*
+ * Weak pointers to repository internals.
+ *
+ * The returned pointers do not need to be freed. Do not keep
+ * permanent references to these (i.e. between API calls), since they may
+ * become invalidated if the user replaces a repository internal.
+ */
+int git_repository_config__weakptr(git_config **out, git_repository *repo);
+int git_repository_odb__weakptr(git_odb **out, git_repository *repo);
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo);
+int git_repository_index__weakptr(git_index **out, git_repository *repo);
+
+/*
+ * CVAR cache
+ *
+ * Efficient access to the most used config variables of a repository.
+ * The cache is cleared every time the config backend is replaced.
+ */
+int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar);
+void git_repository__cvar_cache_clear(git_repository *repo);
+
+GIT_INLINE(int) git_repository__ensure_not_bare(
+ git_repository *repo,
+ const char *operation_name)
+{
+ if (!git_repository_is_bare(repo))
+ return 0;
+
+ giterr_set(
+ GITERR_REPOSITORY,
+ "Cannot %s. This operation is not allowed against bare repositories.",
+ operation_name);
+
+ return GIT_EBAREREPO;
+}
+
+int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head);
+
+int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len);
+
+/* The default "reserved names" for a repository */
+extern git_buf git_repository__reserved_names_win32[];
+extern size_t git_repository__reserved_names_win32_len;
+
+extern git_buf git_repository__reserved_names_posix[];
+extern size_t git_repository__reserved_names_posix_len;
+
+/*
+ * Gets any "reserved names" in the repository. This will return paths
+ * that should not be allowed in the repository (like ".git") to avoid
+ * conflicting with the repository path, or with alternate mechanisms to
+ * the repository path (eg, "GIT~1"). Every attempt will be made to look
+ * up all possible reserved names - if there was a conflict for the shortname
+ * GIT~1, for example, this function will try to look up the alternate
+ * shortname. If that fails, this function returns false, but out and outlen
+ * will still be populated with good defaults.
+ */
+bool git_repository__reserved_names(
+ git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "merge.h"
+#include "diff.h"
+#include "annotated_commit.h"
+#include "git2/reset.h"
+#include "git2/checkout.h"
+#include "git2/merge.h"
+#include "git2/refs.h"
+
+#define ERROR_MSG "Cannot perform reset"
+
+int git_reset_default(
+ git_repository *repo,
+ git_object *target,
+ git_strarray* pathspecs)
+{
+ git_object *commit = NULL;
+ git_tree *tree = NULL;
+ git_diff *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ size_t i, max_i;
+ git_index_entry entry;
+ int error;
+ git_index *index = NULL;
+
+ assert(pathspecs != NULL && pathspecs->count > 0);
+
+ memset(&entry, 0, sizeof(git_index_entry));
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if (target) {
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s_default - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+ }
+
+ opts.pathspec = *pathspecs;
+ opts.flags = GIT_DIFF_REVERSE;
+
+ if ((error = git_diff_tree_to_index(
+ &diff, repo, tree, index, &opts)) < 0)
+ goto cleanup;
+
+ for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) {
+ const git_diff_delta *delta = git_diff_get_delta(diff, i);
+
+ assert(delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_MODIFIED ||
+ delta->status == GIT_DELTA_CONFLICTED ||
+ delta->status == GIT_DELTA_DELETED);
+
+ error = git_index_conflict_remove(index, delta->old_file.path);
+ if (error < 0) {
+ if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND)
+ giterr_clear();
+ else
+ goto cleanup;
+ }
+
+ if (delta->status == GIT_DELTA_DELETED) {
+ if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
+ goto cleanup;
+ } else {
+ entry.mode = delta->new_file.mode;
+ git_oid_cpy(&entry.id, &delta->new_file.id);
+ entry.path = (char *)delta->new_file.path;
+
+ if ((error = git_index_add(index, &entry)) < 0)
+ goto cleanup;
+ }
+ }
+
+ error = git_index_write(index);
+
+cleanup:
+ git_object_free(commit);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_diff_free(diff);
+
+ return error;
+}
+
+static int reset(
+ git_repository *repo,
+ git_object *target,
+ const char *to,
+ git_reset_t reset_type,
+ const git_checkout_options *checkout_opts)
+{
+ git_object *commit = NULL;
+ git_index *index = NULL;
+ git_tree *tree = NULL;
+ int error = 0;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_buf log_message = GIT_BUF_INIT;
+
+ assert(repo && target);
+
+ if (checkout_opts)
+ opts = *checkout_opts;
+
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if (reset_type != GIT_RESET_SOFT &&
+ (error = git_repository__ensure_not_bare(repo,
+ reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)
+ return error;
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_SOFT &&
+ (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||
+ git_index_has_conflicts(index)))
+ {
+ giterr_set(GITERR_OBJECT, "%s (soft) in the middle of a merge.", ERROR_MSG);
+ error = GIT_EUNMERGED;
+ goto cleanup;
+ }
+
+ if ((error = git_buf_printf(&log_message, "reset: moving to %s", to)) < 0)
+ return error;
+
+ if (reset_type == GIT_RESET_HARD) {
+ /* overwrite working directory with the new tree */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
+ goto cleanup;
+ }
+
+ /* move HEAD to the new target */
+ if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,
+ git_object_id(commit), NULL, git_buf_cstr(&log_message))) < 0)
+ goto cleanup;
+
+ if (reset_type > GIT_RESET_SOFT) {
+ /* reset index to the target content */
+
+ if ((error = git_index_read_tree(index, tree)) < 0 ||
+ (error = git_index_write(index)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_state_cleanup(repo)) < 0) {
+ giterr_set(GITERR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ git_object_free(commit);
+ git_index_free(index);
+ git_tree_free(tree);
+ git_buf_free(&log_message);
+
+ return error;
+}
+
+int git_reset(
+ git_repository *repo,
+ git_object *target,
+ git_reset_t reset_type,
+ const git_checkout_options *checkout_opts)
+{
+ return reset(repo, target, git_oid_tostr_s(git_object_id(target)), reset_type, checkout_opts);
+}
+
+int git_reset_from_annotated(
+ git_repository *repo,
+ git_annotated_commit *commit,
+ git_reset_t reset_type,
+ const git_checkout_options *checkout_opts)
+{
+ return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts);
+}
--- /dev/null
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include "merge.h"
+#include "index.h"
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "git2/revert.h"
+#include "git2/commit.h"
+#include "git2/sys/commit.h"
+
+#define GIT_REVERT_FILE_MODE 0666
+
+static int write_revert_head(
+ git_repository *repo,
+ const char *commit_oidstr)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_REVERT_HEAD_FILE)) >= 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const char *commit_oidstr,
+ const char *commit_msgline)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 ||
+ (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n",
+ commit_msgline, commit_oidstr)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int revert_normalize_opts(
+ git_repository *repo,
+ git_revert_options *opts,
+ const git_revert_options *given,
+ const char *their_label)
+{
+ int error = 0;
+ unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
+ GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ GIT_UNUSED(repo);
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_revert_options));
+ else {
+ git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT;
+ memcpy(opts, &default_opts, sizeof(git_revert_options));
+ }
+
+ if (!opts->checkout_opts.checkout_strategy)
+ opts->checkout_opts.checkout_strategy = default_checkout_strategy;
+
+ if (!opts->checkout_opts.our_label)
+ opts->checkout_opts.our_label = "HEAD";
+
+ if (!opts->checkout_opts.their_label)
+ opts->checkout_opts.their_label = their_label;
+
+ return error;
+}
+
+static int revert_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int revert_seterr(git_commit *commit, const char *fmt)
+{
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(commit_oidstr, git_commit_id(commit));
+ commit_oidstr[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_REVERT, fmt, commit_oidstr);
+
+ return -1;
+}
+
+int git_revert_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *revert_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_opts)
+{
+ git_commit *parent_commit = NULL;
+ git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL;
+ int parent = 0, error = 0;
+
+ assert(out && repo && revert_commit && our_commit);
+
+ if (git_commit_parentcount(revert_commit) > 1) {
+ if (!mainline)
+ return revert_seterr(revert_commit,
+ "Mainline branch is not specified but %s is a merge commit");
+
+ parent = mainline;
+ } else {
+ if (mainline)
+ return revert_seterr(revert_commit,
+ "Mainline branch specified but %s is not a merge commit");
+
+ parent = git_commit_parentcount(revert_commit);
+ }
+
+ if (parent &&
+ ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0))
+ goto done;
+
+ if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 ||
+ (error = git_commit_tree(&our_tree, our_commit)) < 0)
+ goto done;
+
+ error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts);
+
+done:
+ git_tree_free(parent_tree);
+ git_tree_free(our_tree);
+ git_tree_free(revert_tree);
+ git_commit_free(parent_commit);
+
+ return error;
+}
+
+int git_revert(
+ git_repository *repo,
+ git_commit *commit,
+ const git_revert_options *given_opts)
+{
+ git_revert_options opts;
+ git_reference *our_ref = NULL;
+ git_commit *our_commit = NULL;
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+ const char *commit_msg;
+ git_buf their_label = GIT_BUF_INIT;
+ git_index *index = NULL;
+ git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
+ int error;
+
+ assert(repo && commit);
+
+ GITERR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options");
+
+ if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0)
+ return error;
+
+ git_oid_fmt(commit_oidstr, git_commit_id(commit));
+ commit_oidstr[GIT_OID_HEXSZ] = '\0';
+
+ if ((commit_msg = git_commit_summary(commit)) == NULL) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_buf_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 ||
+ (error = revert_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
+ (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 ||
+ (error = write_revert_head(repo, commit_oidstr)) < 0 ||
+ (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 ||
+ (error = git_repository_head(&our_ref, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_revert_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
+ (error = git_merge__check_result(repo, index)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 ||
+ (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 ||
+ (error = git_indexwriter_commit(&indexwriter)) < 0)
+ goto on_error;
+
+ goto done;
+
+on_error:
+ revert_state_cleanup(repo);
+
+done:
+ git_indexwriter_cleanup(&indexwriter);
+ git_index_free(index);
+ git_commit_free(our_commit);
+ git_reference_free(our_ref);
+ git_buf_free(&their_label);
+
+ return error;
+}
+
+int git_revert_init_options(git_revert_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "common.h"
+#include "buffer.h"
+#include "tree.h"
+#include "refdb.h"
+
+#include "git2.h"
+
+static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)
+{
+ git_oid oid;
+
+ if (git_oid_fromstrn(&oid, spec, speclen) < 0)
+ return GIT_ENOTFOUND;
+
+ return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
+}
+
+static int maybe_sha(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ if (speclen != GIT_OID_HEXSZ)
+ return GIT_ENOTFOUND;
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int build_regex(regex_t *regex, const char *pattern)
+{
+ int error;
+
+ if (*pattern == '\0') {
+ giterr_set(GITERR_REGEX, "Empty pattern");
+ return GIT_EINVALIDSPEC;
+ }
+
+ error = p_regcomp(regex, pattern, REG_EXTENDED);
+ if (!error)
+ return 0;
+
+ error = giterr_set_regex(regex, error);
+
+ regfree(regex);
+
+ return error;
+}
+
+static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
+{
+ const char *substr;
+ int error;
+ regex_t regex;
+
+ substr = strstr(spec, "-g");
+
+ if (substr == NULL)
+ return GIT_ENOTFOUND;
+
+ if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
+ return -1;
+
+ error = regexec(®ex, spec, 0, NULL, 0);
+ regfree(®ex);
+
+ if (error)
+ return GIT_ENOTFOUND;
+
+ return maybe_abbrev(out, repo, substr+2);
+}
+
+static int revparse_lookup_object(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
+{
+ int error;
+ git_reference *ref;
+
+ if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND)
+ return error;
+
+ error = git_reference_dwim(&ref, repo, spec);
+ if (!error) {
+
+ error = git_object_lookup(
+ object_out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+
+ if (!error)
+ *reference_out = ref;
+
+ return error;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ if ((strlen(spec) < GIT_OID_HEXSZ) &&
+ ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND))
+ return error;
+
+ if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND)
+ return error;
+
+ giterr_set(GITERR_REFERENCE, "Revspec '%s' not found.", spec);
+ return GIT_ENOTFOUND;
+}
+
+static int try_parse_numeric(int *n, const char *curly_braces_content)
+{
+ int32_t content;
+ const char *end_ptr;
+
+ if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
+ return -1;
+
+ if (*end_ptr != '\0')
+ return -1;
+
+ *n = (int)content;
+ return 0;
+}
+
+static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref = NULL;
+ git_reflog *reflog = NULL;
+ regex_t preg;
+ int error = -1;
+ size_t i, numentries, cur;
+ const git_reflog_entry *entry;
+ const char *msg;
+ regmatch_t regexmatches[2];
+ git_buf buf = GIT_BUF_INIT;
+
+ cur = position;
+
+ if (*identifier != '\0' || *base_ref != NULL)
+ return GIT_EINVALIDSPEC;
+
+ if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
+ return -1;
+
+ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ msg = git_reflog_entry_message(entry);
+ if (!msg)
+ continue;
+
+ if (regexec(&preg, msg, 2, regexmatches, 0))
+ continue;
+
+ cur--;
+
+ if (cur > 0)
+ continue;
+
+ git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
+
+ if ((error = git_reference_dwim(base_ref, repo, git_buf_cstr(&buf))) == 0)
+ goto cleanup;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ error = maybe_abbrev(out, repo, git_buf_cstr(&buf));
+
+ goto cleanup;
+ }
+
+ error = GIT_ENOTFOUND;
+
+cleanup:
+ git_reference_free(ref);
+ git_buf_free(&buf);
+ regfree(&preg);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
+{
+ git_reflog *reflog;
+ size_t numentries;
+ const git_reflog_entry *entry;
+ bool search_by_pos = (identifier <= 100000000);
+
+ if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0)
+ return -1;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ if (search_by_pos) {
+ if (numentries < identifier + 1)
+ goto notfound;
+
+ entry = git_reflog_entry_byindex(reflog, identifier);
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ } else {
+ size_t i;
+ git_time commit_time;
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ commit_time = git_reflog_entry_committer(entry)->when;
+
+ if (commit_time.time > (git_time_t)identifier)
+ continue;
+
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ break;
+ }
+
+ if (i == numentries)
+ goto notfound;
+ }
+
+ git_reflog_free(reflog);
+ return 0;
+
+notfound:
+ giterr_set(
+ GITERR_REFERENCE,
+ "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
+ git_reference_name(ref), numentries, identifier);
+
+ git_reflog_free(reflog);
+ return GIT_ENOTFOUND;
+}
+
+static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref;
+ git_oid oid;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (position == 0) {
+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+ goto cleanup;
+ }
+
+ if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
+ goto cleanup;
+
+ error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
+{
+ git_reference *tracking, *ref;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (!git_reference_is_branch(ref)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if ((error = git_branch_upstream(&tracking, ref)) < 0)
+ goto cleanup;
+
+ *base_ref = tracking;
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content)
+{
+ bool is_numeric;
+ int parsed = 0, error = -1;
+ git_buf identifier = GIT_BUF_INIT;
+ git_time_t timestamp;
+
+ assert(*out == NULL);
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
+
+ if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if (is_numeric) {
+ if (parsed < 0)
+ error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
+ else
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
+
+ goto cleanup;
+ }
+
+ if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
+ error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
+
+ goto cleanup;
+ }
+
+ if (git__date_parse(×tamp, curly_braces_content) < 0)
+ goto cleanup;
+
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
+
+cleanup:
+ git_buf_free(&identifier);
+ return error;
+}
+
+static git_otype parse_obj_type(const char *str)
+{
+ if (!strcmp(str, "commit"))
+ return GIT_OBJ_COMMIT;
+
+ if (!strcmp(str, "tree"))
+ return GIT_OBJ_TREE;
+
+ if (!strcmp(str, "blob"))
+ return GIT_OBJ_BLOB;
+
+ if (!strcmp(str, "tag"))
+ return GIT_OBJ_TAG;
+
+ return GIT_OBJ_BAD;
+}
+
+static int dereference_to_non_tag(git_object **out, git_object *obj)
+{
+ if (git_object_type(obj) == GIT_OBJ_TAG)
+ return git_tag_peel(out, (git_tag *)obj);
+
+ return git_object_dup(out, obj);
+}
+
+static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ if (n == 0) {
+ *out = temp_commit;
+ return 0;
+ }
+
+ error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_linear_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_colon_syntax(
+ git_object **out,
+ git_object *obj,
+ const char *path)
+{
+ git_object *tree;
+ int error = -1;
+ git_tree_entry *entry = NULL;
+
+ if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)
+ return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
+
+ if (*path == '\0') {
+ *out = tree;
+ return 0;
+ }
+
+ /*
+ * TODO: Handle the relative path syntax
+ * (:./relative/path and :../relative/path)
+ */
+ if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
+ goto cleanup;
+
+ error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
+
+cleanup:
+ git_tree_entry_free(entry);
+ git_object_free(tree);
+
+ return error;
+}
+
+static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
+{
+ int error;
+ git_oid oid;
+ git_object *obj;
+
+ while (!(error = git_revwalk_next(&oid, walk))) {
+
+ error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
+ if ((error < 0) && (error != GIT_ENOTFOUND))
+ return -1;
+
+ if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
+ *out = obj;
+ return 0;
+ }
+
+ git_object_free(obj);
+ }
+
+ if (error < 0 && error == GIT_ITEROVER)
+ error = GIT_ENOTFOUND;
+
+ return error;
+}
+
+static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
+{
+ regex_t preg;
+ git_revwalk *walk = NULL;
+ int error;
+
+ if ((error = build_regex(&preg, pattern)) < 0)
+ return error;
+
+ if ((error = git_revwalk_new(&walk, repo)) < 0)
+ goto cleanup;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if (spec_oid == NULL) {
+ if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0)
+ goto cleanup;
+ } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
+ goto cleanup;
+
+ error = walk_and_search(out, walk, &preg);
+
+cleanup:
+ regfree(&preg);
+ git_revwalk_free(walk);
+
+ return error;
+}
+
+static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
+{
+ git_otype expected_type;
+
+ if (*curly_braces_content == '\0')
+ return dereference_to_non_tag(out, obj);
+
+ if (*curly_braces_content == '/')
+ return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
+
+ expected_type = parse_obj_type(curly_braces_content);
+
+ if (expected_type == GIT_OBJ_BAD)
+ return GIT_EINVALIDSPEC;
+
+ return git_object_peel(out, obj, expected_type);
+}
+
+static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == '^' || spec[*pos] == '@');
+
+ (*pos)++;
+
+ if (spec[*pos] == '\0' || spec[*pos] != '{')
+ return GIT_EINVALIDSPEC;
+
+ (*pos)++;
+
+ while (spec[*pos] != '}') {
+ if (spec[*pos] == '\0')
+ return GIT_EINVALIDSPEC;
+
+ git_buf_putc(buf, spec[(*pos)++]);
+ }
+
+ (*pos)++;
+
+ return 0;
+}
+
+static int extract_path(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == ':');
+
+ (*pos)++;
+
+ if (git_buf_puts(buf, spec + *pos) < 0)
+ return -1;
+
+ *pos += git_buf_len(buf);
+
+ return 0;
+}
+
+static int extract_how_many(int *n, const char *spec, size_t *pos)
+{
+ const char *end_ptr;
+ int parsed, accumulated;
+ char kind = spec[*pos];
+
+ assert(spec[*pos] == '^' || spec[*pos] == '~');
+
+ accumulated = 0;
+
+ do {
+ do {
+ (*pos)++;
+ accumulated++;
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ if (git__isdigit(spec[*pos])) {
+ if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)
+ return GIT_EINVALIDSPEC;
+
+ accumulated += (parsed - 1);
+ *pos = end_ptr - spec;
+ }
+
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ *n = accumulated;
+
+ return 0;
+}
+
+static int object_from_reference(git_object **object, git_reference *reference)
+{
+ git_reference *resolved = NULL;
+ int error;
+
+ if (git_reference_resolve(&resolved, reference) < 0)
+ return -1;
+
+ error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);
+ git_reference_free(resolved);
+
+ return error;
+}
+
+static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)
+{
+ int error;
+ git_buf identifier = GIT_BUF_INIT;
+
+ if (*object != NULL)
+ return 0;
+
+ if (*reference != NULL)
+ return object_from_reference(object, *reference);
+
+ if (!allow_empty_identifier && identifier_len == 0)
+ return GIT_EINVALIDSPEC;
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ error = revparse_lookup_object(object, reference, repo, git_buf_cstr(&identifier));
+ git_buf_free(&identifier);
+
+ return error;
+}
+
+static int ensure_base_rev_is_not_known_yet(git_object *object)
+{
+ if (object == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
+{
+ if (object != NULL)
+ return true;
+
+ if (reference != NULL)
+ return true;
+
+ if (identifier_len > 0)
+ return true;
+
+ return false;
+}
+
+static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
+{
+ if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+int revparse__ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ size_t *identifier_len_out,
+ git_repository *repo,
+ const char *spec)
+{
+ size_t pos = 0, identifier_len = 0;
+ int error = -1, n;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_reference *reference = NULL;
+ git_object *base_rev = NULL;
+
+ bool should_return_reference = true;
+
+ assert(object_out && reference_out && repo && spec);
+
+ *object_out = NULL;
+ *reference_out = NULL;
+
+ while (spec[pos]) {
+ switch (spec[pos]) {
+ case '^':
+ should_return_reference = false;
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ } else {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ }
+ break;
+
+ case '~':
+ {
+ git_object *temp_object = NULL;
+
+ should_return_reference = false;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case ':':
+ {
+ git_object *temp_object = NULL;
+
+ should_return_reference = false;
+
+ if ((error = extract_path(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
+ goto cleanup;
+
+ if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+ } else {
+ if (*git_buf_cstr(&buf) == '/') {
+ if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
+ goto cleanup;
+ } else {
+
+ /*
+ * TODO: support merge-stage path lookup (":2:Makefile")
+ * and plain index blob lookup (:i-am/a/blob)
+ */
+ giterr_set(GITERR_INVALID, "Unimplemented");
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+ }
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case '@':
+ {
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
+ goto cleanup;
+
+ if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ if (temp_object != NULL)
+ base_rev = temp_object;
+ break;
+ } else {
+ /* Fall through */
+ }
+ }
+
+ default:
+ if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
+ goto cleanup;
+
+ pos++;
+ identifier_len++;
+ }
+ }
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if (!should_return_reference) {
+ git_reference_free(reference);
+ reference = NULL;
+ }
+
+ *object_out = base_rev;
+ *reference_out = reference;
+ *identifier_len_out = identifier_len;
+ error = 0;
+
+cleanup:
+ if (error) {
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(GITERR_INVALID,
+ "Failed to parse revision specifier - Invalid pattern '%s'", spec);
+
+ git_object_free(base_rev);
+ git_reference_free(reference);
+ }
+
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_revparse_ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
+{
+ int error;
+ size_t identifier_len;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ if ((error = revparse__ext(&obj, &ref, &identifier_len, repo, spec)) < 0)
+ goto cleanup;
+
+ *object_out = obj;
+ *reference_out = ref;
+ GIT_UNUSED(identifier_len);
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+{
+ int error;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ *out = NULL;
+
+ if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0)
+ goto cleanup;
+
+ git_reference_free(ref);
+
+ *out = obj;
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec)
+{
+ const char *dotdot;
+ int error = 0;
+
+ assert(revspec && repo && spec);
+
+ memset(revspec, 0x0, sizeof(*revspec));
+
+ if ((dotdot = strstr(spec, "..")) != NULL) {
+ char *lstr;
+ const char *rstr;
+ revspec->flags = GIT_REVPARSE_RANGE;
+
+ lstr = git__substrdup(spec, dotdot - spec);
+ rstr = dotdot + 2;
+ if (dotdot[2] == '.') {
+ revspec->flags |= GIT_REVPARSE_MERGE_BASE;
+ rstr++;
+ }
+
+ error = git_revparse_single(&revspec->from, repo, lstr);
+ if (!error)
+ error = git_revparse_single(&revspec->to, repo, rstr);
+
+ git__free((void*)lstr);
+ } else {
+ revspec->flags = GIT_REVPARSE_SINGLE;
+ error = git_revparse_single(&revspec->from, repo, spec);
+ }
+
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "odb.h"
+#include "pool.h"
+
+#include "revwalk.h"
+#include "git2/revparse.h"
+#include "merge.h"
+#include "vector.h"
+
+GIT__USE_OIDMAP
+
+git_commit_list_node *git_revwalk__commit_lookup(
+ git_revwalk *walk, const git_oid *oid)
+{
+ git_commit_list_node *commit;
+ khiter_t pos;
+ int ret;
+
+ /* lookup and reserve space if not already present */
+ pos = kh_get(oid, walk->commits, oid);
+ if (pos != kh_end(walk->commits))
+ return kh_value(walk->commits, pos);
+
+ commit = git_commit_list_alloc_node(walk);
+ if (commit == NULL)
+ return NULL;
+
+ git_oid_cpy(&commit->oid, oid);
+
+ pos = kh_put(oid, walk->commits, &commit->oid, &ret);
+ assert(ret != 0);
+ kh_value(walk->commits, pos) = commit;
+
+ return commit;
+}
+
+static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob)
+{
+ git_oid commit_id;
+ int error;
+ git_object *obj, *oobj;
+ git_commit_list_node *commit;
+ git_commit_list *list;
+
+ if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ error = git_object_peel(&obj, oobj, GIT_OBJ_COMMIT);
+ git_object_free(oobj);
+
+ if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) {
+ /* If this comes from e.g. push_glob("tags"), ignore this */
+ if (from_glob)
+ return 0;
+
+ giterr_set(GITERR_INVALID, "Object is not a committish");
+ return -1;
+ }
+ if (error < 0)
+ return error;
+
+ git_oid_cpy(&commit_id, git_object_id(obj));
+ git_object_free(obj);
+
+ commit = git_revwalk__commit_lookup(walk, &commit_id);
+ if (commit == NULL)
+ return -1; /* error already reported by failed lookup */
+
+ /* A previous hide already told us we don't want this commit */
+ if (commit->uninteresting)
+ return 0;
+
+ if (uninteresting)
+ walk->did_hide = 1;
+ else
+ walk->did_push = 1;
+
+ commit->uninteresting = uninteresting;
+ list = walk->user_input;
+ if (git_commit_list_insert(commit, &list) == NULL) {
+ giterr_set_oom();
+ return -1;
+ }
+
+ walk->user_input = list;
+
+ return 0;
+}
+
+int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
+{
+ assert(walk && oid);
+ return push_commit(walk, oid, 0, false);
+}
+
+
+int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
+{
+ assert(walk && oid);
+ return push_commit(walk, oid, 1, false);
+}
+
+static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob)
+{
+ git_oid oid;
+
+ if (git_reference_name_to_id(&oid, walk->repo, refname) < 0)
+ return -1;
+
+ return push_commit(walk, &oid, hide, from_glob);
+}
+
+static int push_glob(git_revwalk *walk, const char *glob, int hide)
+{
+ int error = 0;
+ git_buf buf = GIT_BUF_INIT;
+ git_reference *ref;
+ git_reference_iterator *iter;
+ size_t wildcard;
+
+ assert(walk && glob);
+
+ /* refs/ is implied if not given in the glob */
+ if (git__prefixcmp(glob, GIT_REFS_DIR) != 0)
+ git_buf_joinpath(&buf, GIT_REFS_DIR, glob);
+ else
+ git_buf_puts(&buf, glob);
+ GITERR_CHECK_ALLOC_BUF(&buf);
+
+ /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
+ wildcard = strcspn(glob, "?*[");
+ if (!glob[wildcard])
+ git_buf_put(&buf, "/*", 2);
+
+ if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0)
+ goto out;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ error = push_ref(walk, git_reference_name(ref), hide, true);
+ git_reference_free(ref);
+ if (error < 0)
+ break;
+ }
+ git_reference_iterator_free(iter);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+out:
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_revwalk_push_glob(git_revwalk *walk, const char *glob)
+{
+ assert(walk && glob);
+ return push_glob(walk, glob, 0);
+}
+
+int git_revwalk_hide_glob(git_revwalk *walk, const char *glob)
+{
+ assert(walk && glob);
+ return push_glob(walk, glob, 1);
+}
+
+int git_revwalk_push_head(git_revwalk *walk)
+{
+ assert(walk);
+ return push_ref(walk, GIT_HEAD_FILE, 0, false);
+}
+
+int git_revwalk_hide_head(git_revwalk *walk)
+{
+ assert(walk);
+ return push_ref(walk, GIT_HEAD_FILE, 1, false);
+}
+
+int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
+{
+ assert(walk && refname);
+ return push_ref(walk, refname, 0, false);
+}
+
+int git_revwalk_push_range(git_revwalk *walk, const char *range)
+{
+ git_revspec revspec;
+ int error = 0;
+
+ if ((error = git_revparse(&revspec, walk->repo, range)))
+ return error;
+
+ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
+ /* TODO: support "<commit>...<commit>" */
+ giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = push_commit(walk, git_object_id(revspec.from), 1, false)))
+ goto out;
+
+ error = push_commit(walk, git_object_id(revspec.to), 0, false);
+
+out:
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ return error;
+}
+
+int git_revwalk_hide_ref(git_revwalk *walk, const char *refname)
+{
+ assert(walk && refname);
+ return push_ref(walk, refname, 1, false);
+}
+
+static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit)
+{
+ return git_pqueue_insert(&walk->iterator_time, commit);
+}
+
+static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit)
+{
+ return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
+}
+
+static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk)
+{
+ git_commit_list_node *next;
+
+ if ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
+ *object_out = next;
+ return 0;
+ }
+
+ giterr_clear();
+ return GIT_ITEROVER;
+}
+
+static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk)
+{
+ git_commit_list_node *next;
+
+ while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL) {
+ /* Some commits might become uninteresting after being added to the list */
+ if (!next->uninteresting) {
+ *object_out = next;
+ return 0;
+ }
+ }
+
+ giterr_clear();
+ return GIT_ITEROVER;
+}
+
+static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk)
+{
+ git_commit_list_node *next;
+
+ while ((next = git_commit_list_pop(&walk->iterator_topo)) != NULL) {
+ /* Some commits might become uninteresting after being added to the list */
+ if (!next->uninteresting) {
+ *object_out = next;
+ return 0;
+ }
+ }
+
+ giterr_clear();
+ return GIT_ITEROVER;
+}
+
+static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk)
+{
+ *object_out = git_commit_list_pop(&walk->iterator_reverse);
+ return *object_out ? 0 : GIT_ITEROVER;
+}
+
+static void mark_parents_uninteresting(git_commit_list_node *commit)
+{
+ unsigned short i;
+ git_commit_list *parents = NULL;
+
+ for (i = 0; i < commit->out_degree; i++)
+ git_commit_list_insert(commit->parents[i], &parents);
+
+
+ while (parents) {
+ commit = git_commit_list_pop(&parents);
+
+ while (commit) {
+ if (commit->uninteresting)
+ break;
+
+ commit->uninteresting = 1;
+ /*
+ * If we've reached this commit some other way
+ * already, we need to mark its parents uninteresting
+ * as well.
+ */
+ if (!commit->parents)
+ break;
+
+ for (i = 0; i < commit->out_degree; i++)
+ git_commit_list_insert(commit->parents[i], &parents);
+ commit = commit->parents[0];
+ }
+ }
+}
+
+static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list)
+{
+ unsigned short i;
+ int error;
+
+ if (commit->added)
+ return 0;
+
+ commit->added = 1;
+
+ /*
+ * Go full on in the uninteresting case as we want to include
+ * as many of these as we can.
+ *
+ * Usually we haven't parsed the parent of a parent, but if we
+ * have it we reached it via other means so we want to mark
+ * its parents recursively too.
+ */
+ if (commit->uninteresting) {
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ p->uninteresting = 1;
+
+ /* git does it gently here, but we don't like missing objects */
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+
+ if (p->parents)
+ mark_parents_uninteresting(p);
+
+ p->seen = 1;
+ git_commit_list_insert_by_date(p, list);
+ }
+
+ return 0;
+ }
+
+ /*
+ * Now on to what we do if the commit is indeed
+ * interesting. Here we do want things like first-parent take
+ * effect as this is what we'll be showing.
+ */
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+
+ if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload))
+ continue;
+
+ if (!p->seen) {
+ p->seen = 1;
+ git_commit_list_insert_by_date(p, list);
+ }
+
+ if (walk->first_parent)
+ break;
+ }
+ return 0;
+}
+
+static int everybody_uninteresting(git_commit_list *orig)
+{
+ git_commit_list *list = orig;
+
+ while (list) {
+ git_commit_list_node *commit = list->item;
+ list = list->next;
+ if (!commit->uninteresting)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* How many unintersting commits we want to look at after we run out of interesting ones */
+#define SLOP 5
+
+static int still_interesting(git_commit_list *list, int64_t time, int slop)
+{
+ /* The empty list is pretty boring */
+ if (!list)
+ return 0;
+
+ /*
+ * If the destination list has commits with an earlier date
+ * than our source we want to continue looking.
+ */
+ if (time <= list->item->time)
+ return SLOP;
+
+ /* If we find interesting commits, we reset the slop count */
+ if (!everybody_uninteresting(list))
+ return SLOP;
+
+ /* Everything's uninteresting, reduce the count */
+ return slop - 1;
+}
+
+static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits)
+{
+ int error, slop = SLOP;
+ int64_t time = ~0ll;
+ git_commit_list *list = commits;
+ git_commit_list *newlist = NULL;
+ git_commit_list **p = &newlist;
+
+ while (list) {
+ git_commit_list_node *commit = git_commit_list_pop(&list);
+
+ if ((error = add_parents_to_list(walk, commit, &list)) < 0)
+ return error;
+
+ if (commit->uninteresting) {
+ mark_parents_uninteresting(commit);
+
+ slop = still_interesting(list, time, slop);
+ if (slop)
+ continue;
+
+ break;
+ }
+
+ if (!commit->uninteresting && walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload))
+ continue;
+
+ time = commit->time;
+ p = &git_commit_list_insert(commit, p)->next;
+ }
+
+ git_commit_list_free(&list);
+ *out = newlist;
+ return 0;
+}
+
+static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list)
+{
+ git_commit_list *ll = NULL, *newlist, **pptr;
+ git_commit_list_node *next;
+ git_pqueue queue;
+ git_vector_cmp queue_cmp = NULL;
+ unsigned short i;
+ int error;
+
+ if (walk->sorting & GIT_SORT_TIME)
+ queue_cmp = git_commit_list_time_cmp;
+
+ if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp)))
+ return error;
+
+ /*
+ * Start by resetting the in-degree to 1 for the commits in
+ * our list. We want to go through this list again, so we
+ * store it in the commit list as we extract it from the lower
+ * machinery.
+ */
+ for (ll = list; ll; ll = ll->next) {
+ ll->item->in_degree = 1;
+ }
+
+ /*
+ * Count up how many children each commit has. We limit
+ * ourselves to those commits in the original list (in-degree
+ * of 1) avoiding setting it for any parent that was hidden.
+ */
+ for(ll = list; ll; ll = ll->next) {
+ for (i = 0; i < ll->item->out_degree; ++i) {
+ git_commit_list_node *parent = ll->item->parents[i];
+ if (parent->in_degree)
+ parent->in_degree++;
+ }
+ }
+
+ /*
+ * Now we find the tips i.e. those not reachable from any other node
+ * i.e. those which still have an in-degree of 1.
+ */
+ for(ll = list; ll; ll = ll->next) {
+ if (ll->item->in_degree == 1) {
+ if ((error = git_pqueue_insert(&queue, ll->item)))
+ goto cleanup;
+ }
+ }
+
+ /*
+ * We need to output the tips in the order that they came out of the
+ * traversal, so if we're not doing time-sorting, we need to reverse the
+ * pqueue in order to get them to come out as we inserted them.
+ */
+ if ((walk->sorting & GIT_SORT_TIME) == 0)
+ git_pqueue_reverse(&queue);
+
+
+ pptr = &newlist;
+ newlist = NULL;
+ while ((next = git_pqueue_pop(&queue)) != NULL) {
+ for (i = 0; i < next->out_degree; ++i) {
+ git_commit_list_node *parent = next->parents[i];
+ if (parent->in_degree == 0)
+ continue;
+
+ if (--parent->in_degree == 1) {
+ if ((error = git_pqueue_insert(&queue, parent)))
+ goto cleanup;
+ }
+ }
+
+ /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
+ next->in_degree = 0;
+
+ pptr = &git_commit_list_insert(next, pptr)->next;
+ }
+
+ *out = newlist;
+ error = 0;
+
+cleanup:
+ git_pqueue_free(&queue);
+ return error;
+}
+
+static int prepare_walk(git_revwalk *walk)
+{
+ int error;
+ git_commit_list *list, *commits = NULL;
+ git_commit_list_node *next;
+
+ /* If there were no pushes, we know that the walk is already over */
+ if (!walk->did_push) {
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
+
+ for (list = walk->user_input; list; list = list->next) {
+ git_commit_list_node *commit = list->item;
+ if ((error = git_commit_list_parse(walk, commit)) < 0)
+ return error;
+
+ if (commit->uninteresting)
+ mark_parents_uninteresting(commit);
+
+ if (!commit->seen) {
+ commit->seen = 1;
+ git_commit_list_insert(commit, &commits);
+ }
+ }
+
+ if ((error = limit_list(&commits, walk, commits)) < 0)
+ return error;
+
+ if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
+ error = sort_in_topological_order(&walk->iterator_topo, walk, commits);
+ git_commit_list_free(&commits);
+
+ if (error < 0)
+ return error;
+
+ walk->get_next = &revwalk_next_toposort;
+ } else if (walk->sorting & GIT_SORT_TIME) {
+ for (list = commits; list && !error; list = list->next)
+ error = walk->enqueue(walk, list->item);
+
+ git_commit_list_free(&commits);
+
+ if (error < 0)
+ return error;
+ } else {
+ walk->iterator_rand = commits;
+ walk->get_next = revwalk_next_unsorted;
+ }
+
+ if (walk->sorting & GIT_SORT_REVERSE) {
+
+ while ((error = walk->get_next(&next, walk)) == 0)
+ if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL)
+ return -1;
+
+ if (error != GIT_ITEROVER)
+ return error;
+
+ walk->get_next = &revwalk_next_reverse;
+ }
+
+ walk->walking = 1;
+ return 0;
+}
+
+
+int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
+{
+ git_revwalk *walk = git__calloc(1, sizeof(git_revwalk));
+ GITERR_CHECK_ALLOC(walk);
+
+ walk->commits = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(walk->commits);
+
+ if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ git_pool_init(&walk->commit_pool, COMMIT_ALLOC);
+ walk->get_next = &revwalk_next_unsorted;
+ walk->enqueue = &revwalk_enqueue_unsorted;
+
+ walk->repo = repo;
+
+ if (git_repository_odb(&walk->odb, repo) < 0) {
+ git_revwalk_free(walk);
+ return -1;
+ }
+
+ *revwalk_out = walk;
+ return 0;
+}
+
+void git_revwalk_free(git_revwalk *walk)
+{
+ if (walk == NULL)
+ return;
+
+ git_revwalk_reset(walk);
+ git_odb_free(walk->odb);
+
+ git_oidmap_free(walk->commits);
+ git_pool_clear(&walk->commit_pool);
+ git_pqueue_free(&walk->iterator_time);
+ git__free(walk);
+}
+
+git_repository *git_revwalk_repository(git_revwalk *walk)
+{
+ assert(walk);
+ return walk->repo;
+}
+
+void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
+{
+ assert(walk);
+
+ if (walk->walking)
+ git_revwalk_reset(walk);
+
+ walk->sorting = sort_mode;
+
+ if (walk->sorting & GIT_SORT_TIME) {
+ walk->get_next = &revwalk_next_timesort;
+ walk->enqueue = &revwalk_enqueue_timesort;
+ } else {
+ walk->get_next = &revwalk_next_unsorted;
+ walk->enqueue = &revwalk_enqueue_unsorted;
+ }
+}
+
+void git_revwalk_simplify_first_parent(git_revwalk *walk)
+{
+ walk->first_parent = 1;
+}
+
+int git_revwalk_next(git_oid *oid, git_revwalk *walk)
+{
+ int error;
+ git_commit_list_node *next;
+
+ assert(walk && oid);
+
+ if (!walk->walking) {
+ if ((error = prepare_walk(walk)) < 0)
+ return error;
+ }
+
+ error = walk->get_next(&next, walk);
+
+ if (error == GIT_ITEROVER) {
+ git_revwalk_reset(walk);
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
+
+ if (!error)
+ git_oid_cpy(oid, &next->oid);
+
+ return error;
+}
+
+void git_revwalk_reset(git_revwalk *walk)
+{
+ git_commit_list_node *commit;
+
+ assert(walk);
+
+ kh_foreach_value(walk->commits, commit, {
+ commit->seen = 0;
+ commit->in_degree = 0;
+ commit->topo_delay = 0;
+ commit->uninteresting = 0;
+ commit->added = 0;
+ commit->flags = 0;
+ });
+
+ git_pqueue_clear(&walk->iterator_time);
+ git_commit_list_free(&walk->iterator_topo);
+ git_commit_list_free(&walk->iterator_rand);
+ git_commit_list_free(&walk->iterator_reverse);
+ git_commit_list_free(&walk->user_input);
+ walk->first_parent = 0;
+ walk->walking = 0;
+ walk->did_push = walk->did_hide = 0;
+}
+
+int git_revwalk_add_hide_cb(
+ git_revwalk *walk,
+ git_revwalk_hide_cb hide_cb,
+ void *payload)
+{
+ assert(walk);
+
+ if (walk->walking)
+ git_revwalk_reset(walk);
+
+ if (walk->hide_cb) {
+ /* There is already a callback added */
+ giterr_set(GITERR_INVALID, "There is already a callback added to hide commits in revision walker.");
+ return -1;
+ }
+
+ walk->hide_cb = hide_cb;
+ walk->hide_cb_payload = payload;
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_revwalk_h__
+#define INCLUDE_revwalk_h__
+
+#include "git2/revwalk.h"
+#include "oidmap.h"
+#include "commit_list.h"
+#include "pqueue.h"
+#include "pool.h"
+#include "vector.h"
+
+#include "oidmap.h"
+
+struct git_revwalk {
+ git_repository *repo;
+ git_odb *odb;
+
+ git_oidmap *commits;
+ git_pool commit_pool;
+
+ git_commit_list *iterator_topo;
+ git_commit_list *iterator_rand;
+ git_commit_list *iterator_reverse;
+ git_pqueue iterator_time;
+
+ int (*get_next)(git_commit_list_node **, git_revwalk *);
+ int (*enqueue)(git_revwalk *, git_commit_list_node *);
+
+ unsigned walking:1,
+ first_parent: 1,
+ did_hide: 1,
+ did_push: 1;
+ unsigned int sorting;
+
+ /* the pushes and hides */
+ git_commit_list *user_input;
+
+ /* hide callback */
+ git_revwalk_hide_cb hide_cb;
+ void *hide_cb_payload;
+};
+
+git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_OPENSSL
+# include <openssl/err.h>
+#endif
+
+#include <git2.h>
+#include "common.h"
+#include "sysdir.h"
+#include "cache.h"
+#include "global.h"
+#include "object.h"
+
+void git_libgit2_version(int *major, int *minor, int *rev)
+{
+ *major = LIBGIT2_VER_MAJOR;
+ *minor = LIBGIT2_VER_MINOR;
+ *rev = LIBGIT2_VER_REVISION;
+}
+
+int git_libgit2_features(void)
+{
+ return 0
+#ifdef GIT_THREADS
+ | GIT_FEATURE_THREADS
+#endif
+#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
+ | GIT_FEATURE_HTTPS
+#endif
+#if defined(GIT_SSH)
+ | GIT_FEATURE_SSH
+#endif
+#if defined(GIT_USE_NSEC)
+ | GIT_FEATURE_NSEC
+#endif
+ ;
+}
+
+/* Declarations for tuneable settings */
+extern size_t git_mwindow__window_size;
+extern size_t git_mwindow__mapped_limit;
+
+static int config_level_to_sysdir(int config_level)
+{
+ int val = -1;
+
+ switch (config_level) {
+ case GIT_CONFIG_LEVEL_SYSTEM:
+ val = GIT_SYSDIR_SYSTEM;
+ break;
+ case GIT_CONFIG_LEVEL_XDG:
+ val = GIT_SYSDIR_XDG;
+ break;
+ case GIT_CONFIG_LEVEL_GLOBAL:
+ val = GIT_SYSDIR_GLOBAL;
+ break;
+ case GIT_CONFIG_LEVEL_PROGRAMDATA:
+ val = GIT_SYSDIR_PROGRAMDATA;
+ break;
+ default:
+ giterr_set(
+ GITERR_INVALID, "Invalid config path selector %d", config_level);
+ }
+
+ return val;
+}
+
+extern char *git__user_agent;
+extern char *git__ssl_ciphers;
+
+const char *git_libgit2__user_agent(void)
+{
+ return git__user_agent;
+}
+
+const char *git_libgit2__ssl_ciphers(void)
+{
+ return git__ssl_ciphers;
+}
+
+int git_libgit2_opts(int key, ...)
+{
+ int error = 0;
+ va_list ap;
+
+ va_start(ap, key);
+
+ switch (key) {
+ case GIT_OPT_SET_MWINDOW_SIZE:
+ git_mwindow__window_size = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_SIZE:
+ *(va_arg(ap, size_t *)) = git_mwindow__window_size;
+ break;
+
+ case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
+ git_mwindow__mapped_limit = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
+ *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
+ break;
+
+ case GIT_OPT_GET_SEARCH_PATH:
+ if ((error = config_level_to_sysdir(va_arg(ap, int))) >= 0) {
+ git_buf *out = va_arg(ap, git_buf *);
+ const git_buf *tmp;
+
+ git_buf_sanitize(out);
+ if ((error = git_sysdir_get(&tmp, error)) < 0)
+ break;
+
+ error = git_buf_sets(out, tmp->ptr);
+ }
+ break;
+
+ case GIT_OPT_SET_SEARCH_PATH:
+ if ((error = config_level_to_sysdir(va_arg(ap, int))) >= 0)
+ error = git_sysdir_set(error, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
+ {
+ git_otype type = (git_otype)va_arg(ap, int);
+ size_t size = va_arg(ap, size_t);
+ error = git_cache_set_max_object_size(type, size);
+ break;
+ }
+
+ case GIT_OPT_SET_CACHE_MAX_SIZE:
+ git_cache__max_storage = va_arg(ap, ssize_t);
+ break;
+
+ case GIT_OPT_ENABLE_CACHING:
+ git_cache__enabled = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_GET_CACHED_MEMORY:
+ *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
+ *(va_arg(ap, ssize_t *)) = git_cache__max_storage;
+ break;
+
+ case GIT_OPT_GET_TEMPLATE_PATH:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ const git_buf *tmp;
+
+ git_buf_sanitize(out);
+ if ((error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0)
+ break;
+
+ error = git_buf_sets(out, tmp->ptr);
+ }
+ break;
+
+ case GIT_OPT_SET_TEMPLATE_PATH:
+ error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_SET_SSL_CERT_LOCATIONS:
+#ifdef GIT_OPENSSL
+ {
+ const char *file = va_arg(ap, const char *);
+ const char *path = va_arg(ap, const char *);
+ if (!SSL_CTX_load_verify_locations(git__ssl_ctx, file, path)) {
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ error = -1;
+ }
+ }
+#else
+ giterr_set(GITERR_NET, "cannot set certificate locations: OpenSSL is not enabled");
+ error = -1;
+#endif
+ break;
+ case GIT_OPT_SET_USER_AGENT:
+ git__free(git__user_agent);
+ git__user_agent = git__strdup(va_arg(ap, const char *));
+ if (!git__user_agent) {
+ giterr_set_oom();
+ error = -1;
+ }
+
+ break;
+
+ case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION:
+ git_object__strict_input_validation = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_SET_SSL_CIPHERS:
+#ifdef GIT_OPENSSL
+ {
+ git__free(git__ssl_ciphers);
+ git__ssl_ciphers = git__strdup(va_arg(ap, const char *));
+ if (!git__ssl_ciphers) {
+ giterr_set_oom();
+ error = -1;
+ }
+ }
+#else
+ giterr_set(GITERR_NET, "cannot set custom ciphers: OpenSSL is not enabled");
+ error = -1;
+#endif
+ break;
+
+ case GIT_OPT_GET_USER_AGENT:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ git_buf_sanitize(out);
+ error = git_buf_sets(out, git__user_agent);
+ }
+ break;
+
+ default:
+ giterr_set(GITERR_INVALID, "invalid option key");
+ error = -1;
+ }
+
+ va_end(ap);
+
+ return error;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stdio.h>
+
+#include "sha1_lookup.h"
+#include "common.h"
+#include "oid.h"
+
+/*
+ * Conventional binary search loop looks like this:
+ *
+ * unsigned lo, hi;
+ * do {
+ * unsigned mi = (lo + hi) / 2;
+ * int cmp = "entry pointed at by mi" minus "target";
+ * if (!cmp)
+ * return (mi is the wanted one)
+ * if (cmp > 0)
+ * hi = mi; "mi is larger than target"
+ * else
+ * lo = mi+1; "mi is smaller than target"
+ * } while (lo < hi);
+ *
+ * The invariants are:
+ *
+ * - When entering the loop, lo points at a slot that is never
+ * above the target (it could be at the target), hi points at a
+ * slot that is guaranteed to be above the target (it can never
+ * be at the target).
+ *
+ * - We find a point 'mi' between lo and hi (mi could be the same
+ * as lo, but never can be as same as hi), and check if it hits
+ * the target. There are three cases:
+ *
+ * - if it is a hit, we are happy.
+ *
+ * - if it is strictly higher than the target, we set it to hi,
+ * and repeat the search.
+ *
+ * - if it is strictly lower than the target, we update lo to
+ * one slot after it, because we allow lo to be at the target.
+ *
+ * If the loop exits, there is no matching entry.
+ *
+ * When choosing 'mi', we do not have to take the "middle" but
+ * anywhere in between lo and hi, as long as lo <= mi < hi is
+ * satisfied. When we somehow know that the distance between the
+ * target and lo is much shorter than the target and hi, we could
+ * pick mi that is much closer to lo than the midway.
+ *
+ * Now, we can take advantage of the fact that SHA-1 is a good hash
+ * function, and as long as there are enough entries in the table, we
+ * can expect uniform distribution. An entry that begins with for
+ * example "deadbeef..." is much likely to appear much later than in
+ * the midway of the table. It can reasonably be expected to be near
+ * 87% (222/256) from the top of the table.
+ *
+ * However, we do not want to pick "mi" too precisely. If the entry at
+ * the 87% in the above example turns out to be higher than the target
+ * we are looking for, we would end up narrowing the search space down
+ * only by 13%, instead of 50% we would get if we did a simple binary
+ * search. So we would want to hedge our bets by being less aggressive.
+ *
+ * The table at "table" holds at least "nr" entries of "elem_size"
+ * bytes each. Each entry has the SHA-1 key at "key_offset". The
+ * table is sorted by the SHA-1 key of the entries. The caller wants
+ * to find the entry with "key", and knows that the entry at "lo" is
+ * not higher than the entry it is looking for, and that the entry at
+ * "hi" is higher than the entry it is looking for.
+ */
+int sha1_entry_pos(const void *table,
+ size_t elem_size,
+ size_t key_offset,
+ unsigned lo, unsigned hi, unsigned nr,
+ const unsigned char *key)
+{
+ const unsigned char *base = (const unsigned char*)table;
+ const unsigned char *hi_key, *lo_key;
+ unsigned ofs_0;
+
+ if (!nr || lo >= hi)
+ return -1;
+
+ if (nr == hi)
+ hi_key = NULL;
+ else
+ hi_key = base + elem_size * hi + key_offset;
+ lo_key = base + elem_size * lo + key_offset;
+
+ ofs_0 = 0;
+ do {
+ int cmp;
+ unsigned ofs, mi, range;
+ unsigned lov, hiv, kyv;
+ const unsigned char *mi_key;
+
+ range = hi - lo;
+ if (hi_key) {
+ for (ofs = ofs_0; ofs < 20; ofs++)
+ if (lo_key[ofs] != hi_key[ofs])
+ break;
+ ofs_0 = ofs;
+ /*
+ * byte 0 thru (ofs-1) are the same between
+ * lo and hi; ofs is the first byte that is
+ * different.
+ *
+ * If ofs==20, then no bytes are different,
+ * meaning we have entries with duplicate
+ * keys. We know that we are in a solid run
+ * of this entry (because the entries are
+ * sorted, and our lo and hi are the same,
+ * there can be nothing but this single key
+ * in between). So we can stop the search.
+ * Either one of these entries is it (and
+ * we do not care which), or we do not have
+ * it.
+ *
+ * Furthermore, we know that one of our
+ * endpoints must be the edge of the run of
+ * duplicates. For example, given this
+ * sequence:
+ *
+ * idx 0 1 2 3 4 5
+ * key A C C C C D
+ *
+ * If we are searching for "B", we might
+ * hit the duplicate run at lo=1, hi=3
+ * (e.g., by first mi=3, then mi=0). But we
+ * can never have lo > 1, because B < C.
+ * That is, if our key is less than the
+ * run, we know that "lo" is the edge, but
+ * we can say nothing of "hi". Similarly,
+ * if our key is greater than the run, we
+ * know that "hi" is the edge, but we can
+ * say nothing of "lo".
+ *
+ * Therefore if we do not find it, we also
+ * know where it would go if it did exist:
+ * just on the far side of the edge that we
+ * know about.
+ */
+ if (ofs == 20) {
+ mi = lo;
+ mi_key = base + elem_size * mi + key_offset;
+ cmp = memcmp(mi_key, key, 20);
+ if (!cmp)
+ return mi;
+ if (cmp < 0)
+ return -1 - hi;
+ else
+ return -1 - lo;
+ }
+
+ hiv = hi_key[ofs_0];
+ if (ofs_0 < 19)
+ hiv = (hiv << 8) | hi_key[ofs_0+1];
+ } else {
+ hiv = 256;
+ if (ofs_0 < 19)
+ hiv <<= 8;
+ }
+ lov = lo_key[ofs_0];
+ kyv = key[ofs_0];
+ if (ofs_0 < 19) {
+ lov = (lov << 8) | lo_key[ofs_0+1];
+ kyv = (kyv << 8) | key[ofs_0+1];
+ }
+ assert(lov < hiv);
+
+ if (kyv < lov)
+ return -1 - lo;
+ if (hiv < kyv)
+ return -1 - hi;
+
+ /*
+ * Even if we know the target is much closer to 'hi'
+ * than 'lo', if we pick too precisely and overshoot
+ * (e.g. when we know 'mi' is closer to 'hi' than to
+ * 'lo', pick 'mi' that is higher than the target), we
+ * end up narrowing the search space by a smaller
+ * amount (i.e. the distance between 'mi' and 'hi')
+ * than what we would have (i.e. about half of 'lo'
+ * and 'hi'). Hedge our bets to pick 'mi' less
+ * aggressively, i.e. make 'mi' a bit closer to the
+ * middle than we would otherwise pick.
+ */
+ kyv = (kyv * 6 + lov + hiv) / 8;
+ if (lov < hiv - 1) {
+ if (kyv == lov)
+ kyv++;
+ else if (kyv == hiv)
+ kyv--;
+ }
+ mi = (range - 1) * (kyv - lov) / (hiv - lov) + lo;
+
+#ifdef INDEX_DEBUG_LOOKUP
+ printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi);
+ printf("ofs %u lov %x, hiv %x, kyv %x\n",
+ ofs_0, lov, hiv, kyv);
+#endif
+
+ if (!(lo <= mi && mi < hi)) {
+ giterr_set(GITERR_INVALID, "Assertion failure. Binary search invariant is false");
+ return -1;
+ }
+
+ mi_key = base + elem_size * mi + key_offset;
+ cmp = memcmp(mi_key + ofs_0, key + ofs_0, 20 - ofs_0);
+ if (!cmp)
+ return mi;
+ if (cmp > 0) {
+ hi = mi;
+ hi_key = mi_key;
+ } else {
+ lo = mi + 1;
+ lo_key = mi_key + elem_size;
+ }
+ } while (lo < hi);
+ return -((int)lo)-1;
+}
+
+int sha1_position(const void *table,
+ size_t stride,
+ unsigned lo, unsigned hi,
+ const unsigned char *key)
+{
+ const unsigned char *base = table;
+
+ do {
+ unsigned mi = (lo + hi) / 2;
+ int cmp = git_oid__hashcmp(base + mi * stride, key);
+
+ if (!cmp)
+ return mi;
+
+ if (cmp > 0)
+ hi = mi;
+ else
+ lo = mi+1;
+ } while (lo < hi);
+
+ return -((int)lo)-1;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sha1_lookup_h__
+#define INCLUDE_sha1_lookup_h__
+
+#include <stdlib.h>
+
+int sha1_entry_pos(const void *table,
+ size_t elem_size,
+ size_t key_offset,
+ unsigned lo, unsigned hi, unsigned nr,
+ const unsigned char *key);
+
+int sha1_position(const void *table,
+ size_t stride,
+ unsigned lo, unsigned hi,
+ const unsigned char *key);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "signature.h"
+#include "repository.h"
+#include "git2/common.h"
+#include "posix.h"
+
+void git_signature_free(git_signature *sig)
+{
+ if (sig == NULL)
+ return;
+
+ git__free(sig->name);
+ sig->name = NULL;
+ git__free(sig->email);
+ sig->email = NULL;
+ git__free(sig);
+}
+
+static int signature_error(const char *msg)
+{
+ giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg);
+ return -1;
+}
+
+static bool contains_angle_brackets(const char *input)
+{
+ return strchr(input, '<') != NULL || strchr(input, '>') != NULL;
+}
+
+static bool is_crud(unsigned char c)
+{
+ return c <= 32 ||
+ c == '.' ||
+ c == ',' ||
+ c == ':' ||
+ c == ';' ||
+ c == '<' ||
+ c == '>' ||
+ c == '"' ||
+ c == '\\' ||
+ c == '\'';
+}
+
+static char *extract_trimmed(const char *ptr, size_t len)
+{
+ while (len && is_crud((unsigned char)ptr[0])) {
+ ptr++; len--;
+ }
+
+ while (len && is_crud((unsigned char)ptr[len - 1])) {
+ len--;
+ }
+
+ return git__substrdup(ptr, len);
+}
+
+int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset)
+{
+ git_signature *p = NULL;
+
+ assert(name && email);
+
+ *sig_out = NULL;
+
+ if (contains_angle_brackets(name) ||
+ contains_angle_brackets(email)) {
+ return signature_error(
+ "Neither `name` nor `email` should contain angle brackets chars.");
+ }
+
+ p = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(p);
+
+ p->name = extract_trimmed(name, strlen(name));
+ GITERR_CHECK_ALLOC(p->name);
+ p->email = extract_trimmed(email, strlen(email));
+ GITERR_CHECK_ALLOC(p->email);
+
+ if (p->name[0] == '\0' || p->email[0] == '\0') {
+ git_signature_free(p);
+ return signature_error("Signature cannot have an empty name or email");
+ }
+
+ p->when.time = time;
+ p->when.offset = offset;
+
+ *sig_out = p;
+ return 0;
+}
+
+int git_signature_dup(git_signature **dest, const git_signature *source)
+{
+ git_signature *signature;
+
+ if (source == NULL)
+ return 0;
+
+ signature = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(signature);
+
+ signature->name = git__strdup(source->name);
+ GITERR_CHECK_ALLOC(signature->name);
+
+ signature->email = git__strdup(source->email);
+ GITERR_CHECK_ALLOC(signature->email);
+
+ signature->when.time = source->when.time;
+ signature->when.offset = source->when.offset;
+
+ *dest = signature;
+
+ return 0;
+}
+
+int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool)
+{
+ git_signature *signature;
+
+ if (source == NULL)
+ return 0;
+
+ signature = git_pool_mallocz(pool, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(signature);
+
+ signature->name = git_pool_strdup(pool, source->name);
+ GITERR_CHECK_ALLOC(signature->name);
+
+ signature->email = git_pool_strdup(pool, source->email);
+ GITERR_CHECK_ALLOC(signature->email);
+
+ signature->when.time = source->when.time;
+ signature->when.offset = source->when.offset;
+
+ *dest = signature;
+
+ return 0;
+}
+
+int git_signature_now(git_signature **sig_out, const char *name, const char *email)
+{
+ time_t now;
+ time_t offset;
+ struct tm *utc_tm;
+ git_signature *sig;
+ struct tm _utc;
+
+ *sig_out = NULL;
+
+ /*
+ * Get the current time as seconds since the epoch and
+ * transform that into a tm struct containing the time at
+ * UTC. Give that to mktime which considers it a local time
+ * (tm_isdst = -1 asks it to take DST into account) and gives
+ * us that time as seconds since the epoch. The difference
+ * between its return value and 'now' is our offset to UTC.
+ */
+ time(&now);
+ utc_tm = p_gmtime_r(&now, &_utc);
+ utc_tm->tm_isdst = -1;
+ offset = (time_t)difftime(now, mktime(utc_tm));
+ offset /= 60;
+
+ if (git_signature_new(&sig, name, email, now, (int)offset) < 0)
+ return -1;
+
+ *sig_out = sig;
+
+ return 0;
+}
+
+int git_signature_default(git_signature **out, git_repository *repo)
+{
+ int error;
+ git_config *cfg;
+ const char *user_name, *user_email;
+
+ if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
+ return error;
+
+ if (!(error = git_config_get_string(&user_name, cfg, "user.name")) &&
+ !(error = git_config_get_string(&user_email, cfg, "user.email")))
+ error = git_signature_now(out, user_name, user_email);
+
+ git_config_free(cfg);
+ return error;
+}
+
+int git_signature__parse(git_signature *sig, const char **buffer_out,
+ const char *buffer_end, const char *header, char ender)
+{
+ const char *buffer = *buffer_out;
+ const char *email_start, *email_end;
+
+ memset(sig, 0, sizeof(git_signature));
+
+ if (ender &&
+ (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
+ return signature_error("no newline given");
+
+ if (header) {
+ const size_t header_len = strlen(header);
+
+ if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0)
+ return signature_error("expected prefix doesn't match actual");
+
+ buffer += header_len;
+ }
+
+ email_start = git__memrchr(buffer, '<', buffer_end - buffer);
+ email_end = git__memrchr(buffer, '>', buffer_end - buffer);
+
+ if (!email_start || !email_end || email_end <= email_start)
+ return signature_error("malformed e-mail");
+
+ email_start += 1;
+ sig->name = extract_trimmed(buffer, email_start - buffer - 1);
+ sig->email = extract_trimmed(email_start, email_end - email_start);
+
+ /* Do we even have a time at the end of the signature? */
+ if (email_end + 2 < buffer_end) {
+ const char *time_start = email_end + 2;
+ const char *time_end;
+
+ if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)
+ return signature_error("invalid Unix timestamp");
+
+ /* do we have a timezone? */
+ if (time_end + 1 < buffer_end) {
+ int offset, hours, mins;
+ const char *tz_start, *tz_end;
+
+ tz_start = time_end + 1;
+
+ if ((tz_start[0] != '-' && tz_start[0] != '+') ||
+ git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) {
+ //malformed timezone, just assume it's zero
+ offset = 0;
+ }
+
+ hours = offset / 100;
+ mins = offset % 100;
+
+ /*
+ * only store timezone if it's not overflowing;
+ * see http://www.worldtimezone.com/faq.html
+ */
+ if (hours <= 14 && mins <= 59) {
+ sig->when.offset = (hours * 60) + mins;
+ if (tz_start[0] == '-')
+ sig->when.offset = -sig->when.offset;
+ }
+ }
+ }
+
+ *buffer_out = buffer_end + 1;
+ return 0;
+}
+
+int git_signature_from_buffer(git_signature **out, const char *buf)
+{
+ git_signature *sig;
+ const char *buf_end;
+ int error;
+
+ assert(out && buf);
+
+ *out = NULL;
+
+ sig = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(sig);
+
+ buf_end = buf + strlen(buf);
+ error = git_signature__parse(sig, &buf, buf_end, NULL, '\0');
+
+ if (error)
+ git__free(sig);
+ else
+ *out = sig;
+
+ return error;
+}
+
+void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig)
+{
+ int offset, hours, mins;
+ char sign;
+
+ assert(buf && sig);
+
+ offset = sig->when.offset;
+ sign = (sig->when.offset < 0) ? '-' : '+';
+
+ if (offset < 0)
+ offset = -offset;
+
+ hours = offset / 60;
+ mins = offset % 60;
+
+ git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n",
+ header ? header : "", sig->name, sig->email,
+ (unsigned)sig->when.time, sign, hours, mins);
+}
+
+bool git_signature__equal(const git_signature *one, const git_signature *two)
+{
+ assert(one && two);
+
+ return
+ git__strcmp(one->name, two->name) == 0 &&
+ git__strcmp(one->email, two->email) == 0 &&
+ one->when.time == two->when.time &&
+ one->when.offset == two->when.offset;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_signature_h__
+#define INCLUDE_signature_h__
+
+#include "git2/common.h"
+#include "git2/signature.h"
+#include "repository.h"
+#include <time.h>
+
+int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender);
+void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig);
+bool git_signature__equal(const git_signature *one, const git_signature *two);
+
+int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+#include "netops.h"
+#include "stream.h"
+#include "socket_stream.h"
+
+#ifndef _WIN32
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <sys/time.h>
+# include <netdb.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+#else
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
+#endif
+
+#ifdef GIT_WIN32
+static void net_set_error(const char *str)
+{
+ int error = WSAGetLastError();
+ char * win32_error = git_win32_get_error_message(error);
+
+ if (win32_error) {
+ giterr_set(GITERR_NET, "%s: %s", str, win32_error);
+ git__free(win32_error);
+ } else {
+ giterr_set(GITERR_NET, str);
+ }
+}
+#else
+static void net_set_error(const char *str)
+{
+ giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
+}
+#endif
+
+static int close_socket(GIT_SOCKET s)
+{
+ if (s == INVALID_SOCKET)
+ return 0;
+
+#ifdef GIT_WIN32
+ if (SOCKET_ERROR == closesocket(s))
+ return -1;
+
+ if (0 != WSACleanup()) {
+ giterr_set(GITERR_OS, "Winsock cleanup failed");
+ return -1;
+ }
+
+ return 0;
+#else
+ return close(s);
+#endif
+
+}
+
+int socket_connect(git_stream *stream)
+{
+ struct addrinfo *info = NULL, *p;
+ struct addrinfo hints;
+ git_socket_stream *st = (git_socket_stream *) stream;
+ GIT_SOCKET s = INVALID_SOCKET;
+ int ret;
+
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+
+ if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
+ WSACleanup();
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+#endif
+
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
+
+ if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) {
+ giterr_set(GITERR_NET,
+ "Failed to resolve address for %s: %s", st->host, p_gai_strerror(ret));
+ return -1;
+ }
+
+ for (p = info; p != NULL; p = p->ai_next) {
+ s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+
+ if (s == INVALID_SOCKET) {
+ net_set_error("error creating socket");
+ break;
+ }
+
+ if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
+ break;
+
+ /* If we can't connect, try the next one */
+ close_socket(s);
+ s = INVALID_SOCKET;
+ }
+
+ /* Oops, we couldn't connect to any address */
+ if (s == INVALID_SOCKET && p == NULL) {
+ giterr_set(GITERR_OS, "Failed to connect to %s", st->host);
+ p_freeaddrinfo(info);
+ return -1;
+ }
+
+ st->s = s;
+ p_freeaddrinfo(info);
+ return 0;
+}
+
+ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ ssize_t ret;
+ size_t off = 0;
+ git_socket_stream *st = (git_socket_stream *) stream;
+
+ while (off < len) {
+ errno = 0;
+ ret = p_send(st->s, data + off, len - off, flags);
+ if (ret < 0) {
+ net_set_error("Error sending data");
+ return -1;
+ }
+
+ off += ret;
+ }
+
+ return off;
+}
+
+ssize_t socket_read(git_stream *stream, void *data, size_t len)
+{
+ ssize_t ret;
+ git_socket_stream *st = (git_socket_stream *) stream;
+
+ if ((ret = p_recv(st->s, data, len, 0)) < 0)
+ net_set_error("Error receiving socket data");
+
+ return ret;
+}
+
+int socket_close(git_stream *stream)
+{
+ git_socket_stream *st = (git_socket_stream *) stream;
+ int error;
+
+ error = close_socket(st->s);
+ st->s = INVALID_SOCKET;
+
+ return error;
+}
+
+void socket_free(git_stream *stream)
+{
+ git_socket_stream *st = (git_socket_stream *) stream;
+
+ git__free(st->host);
+ git__free(st->port);
+ git__free(st);
+}
+
+int git_socket_stream_new(git_stream **out, const char *host, const char *port)
+{
+ git_socket_stream *st;
+
+ assert(out && host);
+
+ st = git__calloc(1, sizeof(git_socket_stream));
+ GITERR_CHECK_ALLOC(st);
+
+ st->host = git__strdup(host);
+ GITERR_CHECK_ALLOC(st->host);
+
+ if (port) {
+ st->port = git__strdup(port);
+ GITERR_CHECK_ALLOC(st->port);
+ }
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.connect = socket_connect;
+ st->parent.write = socket_write;
+ st->parent.read = socket_read;
+ st->parent.close = socket_close;
+ st->parent.free = socket_free;
+ st->s = INVALID_SOCKET;
+
+ *out = (git_stream *) st;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_socket_stream_h__
+#define INCLUDE_socket_stream_h__
+
+#include "netops.h"
+
+typedef struct {
+ git_stream parent;
+ char *host;
+ char *port;
+ GIT_SOCKET s;
+} git_socket_stream;
+
+extern int git_socket_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
--- /dev/null
+#include "sortedcache.h"
+
+GIT__USE_STRMAP
+
+int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset,
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path)
+{
+ git_sortedcache *sc;
+ size_t pathlen, alloclen;
+
+ pathlen = path ? strlen(path) : 0;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ sc = git__calloc(1, alloclen);
+ GITERR_CHECK_ALLOC(sc);
+
+ git_pool_init(&sc->pool, 1);
+
+ if (git_vector_init(&sc->items, 4, item_cmp) < 0 ||
+ git_strmap_alloc(&sc->map) < 0)
+ goto fail;
+
+ if (git_rwlock_init(&sc->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize lock");
+ goto fail;
+ }
+
+ sc->item_path_offset = item_path_offset;
+ sc->free_item = free_item;
+ sc->free_item_payload = free_item_payload;
+ GIT_REFCOUNT_INC(sc);
+ if (pathlen)
+ memcpy(sc->path, path, pathlen);
+
+ *out = sc;
+ return 0;
+
+fail:
+ git_strmap_free(sc->map);
+ git_vector_free(&sc->items);
+ git_pool_clear(&sc->pool);
+ git__free(sc);
+ return -1;
+}
+
+void git_sortedcache_incref(git_sortedcache *sc)
+{
+ GIT_REFCOUNT_INC(sc);
+}
+
+const char *git_sortedcache_path(git_sortedcache *sc)
+{
+ return sc->path;
+}
+
+static void sortedcache_clear(git_sortedcache *sc)
+{
+ git_strmap_clear(sc->map);
+
+ if (sc->free_item) {
+ size_t i;
+ void *item;
+
+ git_vector_foreach(&sc->items, i, item) {
+ sc->free_item(sc->free_item_payload, item);
+ }
+ }
+
+ git_vector_clear(&sc->items);
+
+ git_pool_clear(&sc->pool);
+}
+
+static void sortedcache_free(git_sortedcache *sc)
+{
+ /* acquire write lock to make sure everyone else is done */
+ if (git_sortedcache_wlock(sc) < 0)
+ return;
+
+ sortedcache_clear(sc);
+ git_vector_free(&sc->items);
+ git_strmap_free(sc->map);
+
+ git_sortedcache_wunlock(sc);
+
+ git_rwlock_free(&sc->lock);
+ git__free(sc);
+}
+
+void git_sortedcache_free(git_sortedcache *sc)
+{
+ if (!sc)
+ return;
+ GIT_REFCOUNT_DEC(sc, sortedcache_free);
+}
+
+static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item)
+{
+ git_sortedcache *sc = payload;
+ /* path will already have been copied by upsert */
+ memcpy(tgt_item, src_item, sc->item_path_offset);
+ return 0;
+}
+
+/* copy a sorted cache */
+int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload)
+{
+ int error = 0;
+ git_sortedcache *tgt;
+ size_t i;
+ void *src_item, *tgt_item;
+
+ /* just use memcpy if no special copy fn is passed in */
+ if (!copy_item) {
+ copy_item = sortedcache_copy_item;
+ payload = src;
+ }
+
+ if ((error = git_sortedcache_new(
+ &tgt, src->item_path_offset,
+ src->free_item, src->free_item_payload,
+ src->items._cmp, src->path)) < 0)
+ return error;
+
+ if (lock && git_sortedcache_rlock(src) < 0) {
+ git_sortedcache_free(tgt);
+ return -1;
+ }
+
+ git_vector_foreach(&src->items, i, src_item) {
+ char *path = ((char *)src_item) + src->item_path_offset;
+
+ if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 ||
+ (error = copy_item(payload, tgt_item, src_item)) < 0)
+ break;
+ }
+
+ if (lock)
+ git_sortedcache_runlock(src);
+ if (error)
+ git_sortedcache_free(tgt);
+
+ *out = !error ? tgt : NULL;
+
+ return error;
+}
+
+/* lock sortedcache while making modifications */
+int git_sortedcache_wlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_wrlock(&sc->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire write lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done with modifications */
+void git_sortedcache_wunlock(git_sortedcache *sc)
+{
+ git_vector_sort(&sc->items);
+ git_rwlock_wrunlock(&sc->lock);
+}
+
+/* lock sortedcache for read */
+int git_sortedcache_rlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_rdlock(&sc->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire read lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done reading */
+void git_sortedcache_runlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+ git_rwlock_rdunlock(&sc->lock);
+}
+
+/* if the file has changed, lock cache and load file contents into buf;
+ * returns <0 on error, >0 if file has not changed
+ */
+int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf)
+{
+ int error, fd;
+ struct stat st;
+
+ if ((error = git_sortedcache_wlock(sc)) < 0)
+ return error;
+
+ if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
+ goto unlock;
+
+ if ((fd = git_futils_open_ro(sc->path)) < 0) {
+ error = fd;
+ goto unlock;
+ }
+
+ if (p_fstat(fd, &st) < 0) {
+ giterr_set(GITERR_OS, "failed to stat file");
+ error = -1;
+ (void)p_close(fd);
+ goto unlock;
+ }
+
+ if (!git__is_sizet(st.st_size)) {
+ giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
+ error = -1;
+ (void)p_close(fd);
+ goto unlock;
+ }
+
+ if (buf)
+ error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
+
+ (void)p_close(fd);
+
+ if (error < 0)
+ goto unlock;
+
+ return 1; /* return 1 -> file needs reload and was successfully loaded */
+
+unlock:
+ git_sortedcache_wunlock(sc);
+ return error;
+}
+
+void git_sortedcache_updated(git_sortedcache *sc)
+{
+ /* update filestamp to latest value */
+ git_futils_filestamp_check(&sc->stamp, sc->path);
+}
+
+/* release all items in sorted cache */
+int git_sortedcache_clear(git_sortedcache *sc, bool wlock)
+{
+ if (wlock && git_sortedcache_wlock(sc) < 0)
+ return -1;
+
+ sortedcache_clear(sc);
+
+ if (wlock)
+ git_sortedcache_wunlock(sc);
+
+ return 0;
+}
+
+/* find and/or insert item, returning pointer to item data */
+int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key)
+{
+ int error = 0;
+ khiter_t pos;
+ void *item;
+ size_t keylen, itemlen;
+ char *item_key;
+
+ pos = git_strmap_lookup_index(sc->map, key);
+ if (git_strmap_valid_index(sc->map, pos)) {
+ item = git_strmap_value_at(sc->map, pos);
+ goto done;
+ }
+
+ keylen = strlen(key);
+ itemlen = sc->item_path_offset + keylen + 1;
+ itemlen = (itemlen + 7) & ~7;
+
+ if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) {
+ /* don't use GITERR_CHECK_ALLOC b/c of lock */
+ error = -1;
+ goto done;
+ }
+
+ /* one strange thing is that even if the vector or hash table insert
+ * fail, there is no way to free the pool item so we just abandon it
+ */
+
+ item_key = ((char *)item) + sc->item_path_offset;
+ memcpy(item_key, key, keylen);
+
+ pos = kh_put(str, sc->map, item_key, &error);
+ if (error < 0)
+ goto done;
+
+ if (!error)
+ kh_key(sc->map, pos) = item_key;
+ kh_val(sc->map, pos) = item;
+
+ error = git_vector_insert(&sc->items, item);
+ if (error < 0)
+ git_strmap_delete_at(sc->map, pos);
+
+done:
+ if (out)
+ *out = !error ? item : NULL;
+ return error;
+}
+
+/* lookup item by key */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key)
+{
+ khiter_t pos = git_strmap_lookup_index(sc->map, key);
+ if (git_strmap_valid_index(sc->map, pos))
+ return git_strmap_value_at(sc->map, pos);
+ return NULL;
+}
+
+/* find out how many items are in the cache */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc)
+{
+ return git_vector_length(&sc->items);
+}
+
+/* lookup item by index */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos)
+{
+ /* make sure the items are sorted so this gets the correct item */
+ if (!git_vector_is_sorted(&sc->items))
+ git_vector_sort(&sc->items);
+
+ return git_vector_get(&sc->items, pos);
+}
+
+/* helper struct so bsearch callback can know offset + key value for cmp */
+struct sortedcache_magic_key {
+ size_t offset;
+ const char *key;
+};
+
+static int sortedcache_magic_cmp(const void *key, const void *value)
+{
+ const struct sortedcache_magic_key *magic = key;
+ const char *value_key = ((const char *)value) + magic->offset;
+ return strcmp(magic->key, value_key);
+}
+
+/* lookup index of item by key */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key)
+{
+ struct sortedcache_magic_key magic;
+
+ magic.offset = sc->item_path_offset;
+ magic.key = key;
+
+ return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic);
+}
+
+/* remove entry from cache */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos)
+{
+ char *item;
+ khiter_t mappos;
+
+ /* because of pool allocation, this can't actually remove the item,
+ * but we can remove it from the items vector and the hash table.
+ */
+
+ if ((item = git_vector_get(&sc->items, pos)) == NULL) {
+ giterr_set(GITERR_INVALID, "Removing item out of range");
+ return GIT_ENOTFOUND;
+ }
+
+ (void)git_vector_remove(&sc->items, pos);
+
+ mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset);
+ git_strmap_delete_at(sc->map, mappos);
+
+ if (sc->free_item)
+ sc->free_item(sc->free_item_payload, item);
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sorted_cache_h__
+#define INCLUDE_sorted_cache_h__
+
+#include "util.h"
+#include "fileops.h"
+#include "vector.h"
+#include "thread-utils.h"
+#include "pool.h"
+#include "strmap.h"
+
+#include <stddef.h>
+
+/*
+ * The purpose of this data structure is to cache the parsed contents of a
+ * file (a.k.a. the backing file) where each item in the file can be
+ * identified by a key string and you want to both look them up by name
+ * and traverse them in sorted order. Each item is assumed to itself end
+ * in a GIT_FLEX_ARRAY.
+ */
+
+typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item);
+
+typedef struct {
+ git_refcount rc;
+ git_rwlock lock;
+ size_t item_path_offset;
+ git_sortedcache_free_item_fn free_item;
+ void *free_item_payload;
+ git_pool pool;
+ git_vector items;
+ git_strmap *map;
+ git_futils_filestamp stamp;
+ char path[GIT_FLEX_ARRAY];
+} git_sortedcache;
+
+/* Create a new sortedcache
+ *
+ * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at
+ * the end containing their key string, you have to provide the item_cmp
+ * sorting function because the sorting function doesn't get a payload
+ * and therefore can't know the offset to the item key string. :-(
+ *
+ * @param out The allocated git_sortedcache
+ * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the
+ * struct - use offsetof(struct mine, key-field) to get this
+ * @param free_item Optional callback to free each item
+ * @param free_item_payload Optional payload passed to free_item callback
+ * @param item_cmp Compare the keys of two items
+ * @param path The path to the backing store file for this cache; this
+ * may be NULL. The cache makes it easy to load this and check
+ * if it has been modified since the last load and/or write.
+ */
+int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset, /* use offsetof(struct, path-field) macro */
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path);
+
+/* Copy a sorted cache
+ *
+ * - `copy_item` can be NULL to just use memcpy
+ * - if `lock`, grabs read lock on `src` during copy and releases after
+ */
+int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload);
+
+/* Free sorted cache (first calling `free_item` callbacks)
+ *
+ * Don't call on a locked collection - it may acquire a write lock
+ */
+void git_sortedcache_free(git_sortedcache *sc);
+
+/* Increment reference count - balance with call to free */
+void git_sortedcache_incref(git_sortedcache *sc);
+
+/* Get the pathname associated with this cache at creation time */
+const char *git_sortedcache_path(git_sortedcache *sc);
+
+/*
+ * CACHE WRITE FUNCTIONS
+ *
+ * The following functions require you to have a writer lock to make the
+ * modification. Some of the functions take a `wlock` parameter and
+ * will optionally lock and unlock for you if that is passed as true.
+ *
+ */
+
+/* Lock sortedcache for write */
+int git_sortedcache_wlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with write */
+void git_sortedcache_wunlock(git_sortedcache *sc);
+
+/* Lock cache and load backing file into a buffer.
+ *
+ * This grabs a write lock on the cache then looks at the modification
+ * time and size of the file on disk.
+ *
+ * If the file appears to have changed, this loads the file contents into
+ * the buffer and returns a positive value leaving the cache locked - the
+ * caller should parse the file content, update the cache as needed, then
+ * release the lock. NOTE: In this case, the caller MUST unlock the cache.
+ *
+ * If the file appears to be unchanged, then this automatically releases
+ * the lock on the cache, clears the buffer, and returns 0.
+ *
+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ */
+int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf);
+
+/* Refresh file timestamp after write completes
+ * You should already be holding the write lock when you call this.
+ */
+void git_sortedcache_updated(git_sortedcache *sc);
+
+/* Release all items in sorted cache
+ *
+ * If `wlock` is true, grabs write lock and releases when done, otherwise
+ * you should already be holding a write lock when you call this.
+ */
+int git_sortedcache_clear(git_sortedcache *sc, bool wlock);
+
+/* Find and/or insert item, returning pointer to item data.
+ * You should already be holding the write lock when you call this.
+ */
+int git_sortedcache_upsert(
+ void **out, git_sortedcache *sc, const char *key);
+
+/* Removes entry at pos from cache
+ * You should already be holding the write lock when you call this.
+ */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos);
+
+/*
+ * CACHE READ FUNCTIONS
+ *
+ * The following functions access items in the cache. To prevent the
+ * results from being invalidated before they can be used, you should be
+ * holding either a read lock or a write lock when using these functions.
+ *
+ */
+
+/* Lock sortedcache for read */
+int git_sortedcache_rlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with read */
+void git_sortedcache_runlock(git_sortedcache *sc);
+
+/* Lookup item by key - returns NULL if not found */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key);
+
+/* Get how many items are in the cache
+ *
+ * You can call this function without holding a lock, but be aware
+ * that it may change before you use it.
+ */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc);
+
+/* Lookup item by index - returns NULL if out of range */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos);
+
+/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "commit.h"
+#include "message.h"
+#include "tree.h"
+#include "reflog.h"
+#include "git2/diff.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/checkout.h"
+#include "git2/index.h"
+#include "git2/transaction.h"
+#include "git2/merge.h"
+#include "index.h"
+#include "signature.h"
+#include "iterator.h"
+#include "merge.h"
+#include "diff.h"
+#include "diff_generate.h"
+
+static int create_error(int error, const char *msg)
+{
+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
+ return error;
+}
+
+static int retrieve_head(git_reference **out, git_repository *repo)
+{
+ int error = git_repository_head(out, repo);
+
+ if (error == GIT_EUNBORNBRANCH)
+ return create_error(error, "You do not have the initial commit yet.");
+
+ return error;
+}
+
+static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
+{
+ char *formatted_oid;
+
+ formatted_oid = git_oid_allocfmt(b_commit);
+ GITERR_CHECK_ALLOC(formatted_oid);
+
+ git_buf_put(out, formatted_oid, 7);
+ git__free(formatted_oid);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int append_commit_description(git_buf *out, git_commit* commit)
+{
+ const char *summary = git_commit_summary(commit);
+ GITERR_CHECK_ALLOC(summary);
+
+ if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
+ return -1;
+
+ git_buf_putc(out, ' ');
+ git_buf_puts(out, summary);
+ git_buf_putc(out, '\n');
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int retrieve_base_commit_and_message(
+ git_commit **b_commit,
+ git_buf *stash_message,
+ git_repository *repo)
+{
+ git_reference *head = NULL;
+ int error;
+
+ if ((error = retrieve_head(&head, repo)) < 0)
+ return error;
+
+ if (strcmp("HEAD", git_reference_name(head)) == 0)
+ error = git_buf_puts(stash_message, "(no branch): ");
+ else
+ error = git_buf_printf(
+ stash_message,
+ "%s: ",
+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_commit_lookup(
+ b_commit, repo, git_reference_target(head))) < 0)
+ goto cleanup;
+
+ if ((error = append_commit_description(stash_message, *b_commit)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+static int build_tree_from_index(git_tree **out, git_index *index)
+{
+ int error;
+ git_oid i_tree_oid;
+
+ if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
+ return error;
+
+ return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
+}
+
+static int commit_index(
+ git_commit **i_commit,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ const git_commit *parent)
+{
+ git_tree *i_tree = NULL;
+ git_oid i_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_tree_from_index(&i_tree, index)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &i_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ i_tree,
+ 1,
+ &parent)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+struct stash_update_rules {
+ bool include_changed;
+ bool include_untracked;
+ bool include_ignored;
+};
+
+static int stash_update_index_from_diff(
+ git_index *index,
+ const git_diff *diff,
+ struct stash_update_rules *data)
+{
+ int error = 0;
+ size_t d, max_d = git_diff_num_deltas(diff);
+
+ for (d = 0; !error && d < max_d; ++d) {
+ const char *add_path = NULL;
+ const git_diff_delta *delta = git_diff_get_delta(diff, d);
+
+ switch (delta->status) {
+ case GIT_DELTA_IGNORED:
+ if (data->include_ignored)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_UNTRACKED:
+ if (data->include_untracked &&
+ delta->new_file.mode != GIT_FILEMODE_TREE)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ if (data->include_changed)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_DELETED:
+ if (data->include_changed &&
+ !git_index_find(NULL, index, delta->old_file.path))
+ error = git_index_remove(index, delta->old_file.path, 0);
+ break;
+
+ default:
+ /* Unimplemented */
+ giterr_set(
+ GITERR_INVALID,
+ "Cannot update index. Unimplemented status (%d)",
+ delta->status);
+ return -1;
+ }
+
+ if (add_path != NULL)
+ error = git_index_add_bypath(index, add_path);
+ }
+
+ return error;
+}
+
+static int build_untracked_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *i_tree = NULL;
+ git_diff *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct stash_update_rules data = {0};
+ int error;
+
+ git_index_clear(index);
+
+ if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ data.include_untracked = true;
+ }
+
+ if (flags & GIT_STASH_INCLUDE_IGNORED) {
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_RECURSE_IGNORED_DIRS;
+ data.include_ignored = true;
+ }
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_workdir(
+ &diff, git_index_owner(index), i_tree, &opts)) < 0)
+ goto cleanup;
+
+ if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
+ goto cleanup;
+
+ error = build_tree_from_index(tree_out, index);
+
+cleanup:
+ git_diff_free(diff);
+ git_tree_free(i_tree);
+ return error;
+}
+
+static int commit_untracked(
+ git_commit **u_commit,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *u_tree = NULL;
+ git_oid u_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &u_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ u_tree,
+ 0,
+ NULL)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
+
+cleanup:
+ git_tree_free(u_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+static git_diff_delta *stash_delta_merge(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool)
+{
+ /* Special case for stash: if a file is deleted in the index, but exists
+ * in the working tree, we need to stash the workdir copy for the workdir.
+ */
+ if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) {
+ git_diff_delta *dup = git_diff__delta_dup(b, pool);
+
+ if (dup)
+ dup->status = GIT_DELTA_MODIFIED;
+ return dup;
+ }
+
+ return git_diff__merge_like_cgit(a, b, pool);
+}
+
+static int build_workdir_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *b_commit)
+{
+ git_repository *repo = git_index_owner(index);
+ git_tree *b_tree = NULL;
+ git_diff *diff = NULL, *idx_to_wd = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct stash_update_rules data = {0};
+ int error;
+
+ opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 ||
+ (error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 ||
+ (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0)
+ goto cleanup;
+
+ data.include_changed = true;
+
+ if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
+ goto cleanup;
+
+ error = build_tree_from_index(tree_out, index);
+
+cleanup:
+ git_diff_free(idx_to_wd);
+ git_diff_free(diff);
+ git_tree_free(b_tree);
+
+ return error;
+}
+
+static int commit_worktree(
+ git_oid *w_commit_oid,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ git_commit *b_commit,
+ git_commit *u_commit)
+{
+ int error = 0;
+ git_tree *w_tree = NULL, *i_tree = NULL;
+ const git_commit *parents[] = { NULL, NULL, NULL };
+
+ parents[0] = b_commit;
+ parents[1] = i_commit;
+ parents[2] = u_commit;
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_index_read_tree(index, i_tree)) < 0)
+ goto cleanup;
+
+ if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
+ goto cleanup;
+
+ error = git_commit_create(
+ w_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ message,
+ w_tree,
+ u_commit ? 3 : 2,
+ parents);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_tree_free(w_tree);
+ return error;
+}
+
+static int prepare_worktree_commit_message(
+ git_buf* msg,
+ const char *user_message)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)
+ return error;
+
+ git_buf_clear(msg);
+
+ if (!user_message)
+ git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));
+ else {
+ const char *colon;
+
+ if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)
+ goto cleanup;
+
+ git_buf_puts(msg, "On ");
+ git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);
+ git_buf_printf(msg, ": %s\n", user_message);
+ }
+
+ error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;
+
+cleanup:
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int update_reflog(
+ git_oid *w_commit_oid,
+ git_repository *repo,
+ const char *message)
+{
+ git_reference *stash;
+ int error;
+
+ if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0)
+ return error;
+
+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message);
+
+ git_reference_free(stash);
+
+ return error;
+}
+
+static int is_dirty_cb(const char *path, unsigned int status, void *payload)
+{
+ GIT_UNUSED(path);
+ GIT_UNUSED(status);
+ GIT_UNUSED(payload);
+
+ return GIT_PASSTHROUGH;
+}
+
+static int ensure_there_are_changes_to_stash(
+ git_repository *repo,
+ bool include_untracked_files,
+ bool include_ignored_files)
+{
+ int error;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+
+ if (include_untracked_files)
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ if (include_ignored_files)
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);
+
+ if (error == GIT_PASSTHROUGH)
+ return 0;
+
+ if (!error)
+ return create_error(GIT_ENOTFOUND, "There is nothing to stash.");
+
+ return error;
+}
+
+static int reset_index_and_workdir(
+ git_repository *repo,
+ git_commit *commit,
+ bool remove_untracked,
+ bool remove_ignored)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ if (remove_untracked)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ if (remove_ignored)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED;
+
+ return git_checkout_tree(repo, (git_object *)commit, &opts);
+}
+
+int git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *stasher,
+ const char *message,
+ uint32_t flags)
+{
+ git_index *index = NULL;
+ git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ assert(out && repo && stasher);
+
+ if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)
+ return error;
+
+ if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_there_are_changes_to_stash(
+ repo,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if ((error = commit_index(
+ &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
+ goto cleanup;
+
+ if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
+ (error = commit_untracked(
+ &u_commit, index, stasher, git_buf_cstr(&msg),
+ i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = prepare_worktree_commit_message(&msg, message)) < 0)
+ goto cleanup;
+
+ if ((error = commit_worktree(
+ out, index, stasher, git_buf_cstr(&msg),
+ i_commit, b_commit, u_commit)) < 0)
+ goto cleanup;
+
+ git_buf_rtrim(&msg);
+
+ if ((error = update_reflog(out, repo, git_buf_cstr(&msg))) < 0)
+ goto cleanup;
+
+ if ((error = reset_index_and_workdir(
+ repo,
+ ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
+ goto cleanup;
+
+cleanup:
+
+ git_buf_free(&msg);
+ git_commit_free(i_commit);
+ git_commit_free(b_commit);
+ git_commit_free(u_commit);
+ git_index_free(index);
+
+ return error;
+}
+
+static int retrieve_stash_commit(
+ git_commit **commit,
+ git_repository *repo,
+ size_t index)
+{
+ git_reference *stash = NULL;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t max;
+ const git_reflog_entry *entry;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ if (!max || index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ entry = git_reflog_entry_byindex(reflog, index);
+ if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_stash_trees(
+ git_tree **out_stash_tree,
+ git_tree **out_base_tree,
+ git_tree **out_index_tree,
+ git_tree **out_index_parent_tree,
+ git_tree **out_untracked_tree,
+ git_commit *stash_commit)
+{
+ git_tree *stash_tree = NULL;
+ git_commit *base_commit = NULL;
+ git_tree *base_tree = NULL;
+ git_commit *index_commit = NULL;
+ git_tree *index_tree = NULL;
+ git_commit *index_parent_commit = NULL;
+ git_tree *index_parent_tree = NULL;
+ git_commit *untracked_commit = NULL;
+ git_tree *untracked_tree = NULL;
+ int error;
+
+ if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&base_tree, base_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&index_tree, index_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0)
+ goto cleanup;
+
+ if (git_commit_parentcount(stash_commit) == 3) {
+ if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0)
+ goto cleanup;
+ }
+
+ *out_stash_tree = stash_tree;
+ *out_base_tree = base_tree;
+ *out_index_tree = index_tree;
+ *out_index_parent_tree = index_parent_tree;
+ *out_untracked_tree = untracked_tree;
+
+cleanup:
+ git_commit_free(untracked_commit);
+ git_commit_free(index_parent_commit);
+ git_commit_free(index_commit);
+ git_commit_free(base_commit);
+ if (error < 0) {
+ git_tree_free(stash_tree);
+ git_tree_free(base_tree);
+ git_tree_free(index_tree);
+ git_tree_free(index_parent_tree);
+ git_tree_free(untracked_tree);
+ }
+ return error;
+}
+
+static int merge_indexes(
+ git_index **out,
+ git_repository *repo,
+ git_tree *ancestor_tree,
+ git_index *ours_index,
+ git_index *theirs_index)
+{
+ git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error;
+
+ iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 ||
+ (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 ||
+ (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0)
+ goto done;
+
+ error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
+
+done:
+ git_iterator_free(ancestor);
+ git_iterator_free(ours);
+ git_iterator_free(theirs);
+ return error;
+}
+
+static int merge_index_and_tree(
+ git_index **out,
+ git_repository *repo,
+ git_tree *ancestor_tree,
+ git_index *ours_index,
+ git_tree *theirs_tree)
+{
+ git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error;
+
+ iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 ||
+ (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 ||
+ (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0)
+ goto done;
+
+ error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
+
+done:
+ git_iterator_free(ancestor);
+ git_iterator_free(ours);
+ git_iterator_free(theirs);
+ return error;
+}
+
+static void normalize_apply_options(
+ git_stash_apply_options *opts,
+ const git_stash_apply_options *given_apply_opts)
+{
+ if (given_apply_opts != NULL) {
+ memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options));
+ } else {
+ git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT;
+ memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options));
+ }
+
+ if ((opts->checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0)
+ opts->checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ if (!opts->checkout_options.our_label)
+ opts->checkout_options.our_label = "Updated upstream";
+
+ if (!opts->checkout_options.their_label)
+ opts->checkout_options.their_label = "Stashed changes";
+}
+
+int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT);
+ return 0;
+}
+
+#define NOTIFY_PROGRESS(opts, progress_type) \
+ do { \
+ if ((opts).progress_cb && \
+ (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \
+ error = (error < 0) ? error : -1; \
+ goto cleanup; \
+ } \
+ } while(false);
+
+static int ensure_clean_index(git_repository *repo, git_index *index)
+{
+ git_tree *head_tree = NULL;
+ git_diff *index_diff = NULL;
+ int error = 0;
+
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_diff_tree_to_index(
+ &index_diff, repo, head_tree, index, NULL)) < 0)
+ goto done;
+
+ if (git_diff_num_deltas(index_diff) > 0) {
+ giterr_set(GITERR_STASH, "%" PRIuZ " uncommitted changes exist in the index",
+ git_diff_num_deltas(index_diff));
+ error = GIT_EUNCOMMITTED;
+ }
+
+done:
+ git_diff_free(index_diff);
+ git_tree_free(head_tree);
+ return error;
+}
+
+static int stage_new_file(const git_index_entry **entries, void *data)
+{
+ git_index *index = data;
+
+ if(entries[0] == NULL)
+ return git_index_add(index, entries[1]);
+ else
+ return git_index_add(index, entries[0]);
+}
+
+static int stage_new_files(
+ git_index **out,
+ git_tree *parent_tree,
+ git_tree *tree)
+{
+ git_iterator *iterators[2] = { NULL, NULL };
+ git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index = NULL;
+ int error;
+
+ if ((error = git_index_new(&index)) < 0 ||
+ (error = git_iterator_for_tree(
+ &iterators[0], parent_tree, &iterator_options)) < 0 ||
+ (error = git_iterator_for_tree(
+ &iterators[1], tree, &iterator_options)) < 0)
+ goto done;
+
+ error = git_iterator_walk(iterators, 2, stage_new_file, index);
+
+done:
+ if (error < 0)
+ git_index_free(index);
+ else
+ *out = index;
+
+ git_iterator_free(iterators[0]);
+ git_iterator_free(iterators[1]);
+
+ return error;
+}
+
+int git_stash_apply(
+ git_repository *repo,
+ size_t index,
+ const git_stash_apply_options *given_opts)
+{
+ git_stash_apply_options opts;
+ unsigned int checkout_strategy;
+ git_commit *stash_commit = NULL;
+ git_tree *stash_tree = NULL;
+ git_tree *stash_parent_tree = NULL;
+ git_tree *index_tree = NULL;
+ git_tree *index_parent_tree = NULL;
+ git_tree *untracked_tree = NULL;
+ git_index *stash_adds = NULL;
+ git_index *repo_index = NULL;
+ git_index *unstashed_index = NULL;
+ git_index *modified_index = NULL;
+ git_index *untracked_index = NULL;
+ int error;
+
+ GITERR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options");
+
+ normalize_apply_options(&opts, given_opts);
+ checkout_strategy = opts.checkout_options.checkout_strategy;
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH);
+
+ /* Retrieve commit corresponding to the given stash */
+ if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0)
+ goto cleanup;
+
+ /* Retrieve all trees in the stash */
+ if ((error = retrieve_stash_trees(
+ &stash_tree, &stash_parent_tree, &index_tree,
+ &index_parent_tree, &untracked_tree, stash_commit)) < 0)
+ goto cleanup;
+
+ /* Load repo index */
+ if ((error = git_repository_index(&repo_index, repo)) < 0)
+ goto cleanup;
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX);
+
+ if ((error = ensure_clean_index(repo, repo_index)) < 0)
+ goto cleanup;
+
+ /* Restore index if required */
+ if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) &&
+ git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) {
+
+ if ((error = merge_index_and_tree(
+ &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0)
+ goto cleanup;
+
+ if (git_index_has_conflicts(unstashed_index)) {
+ error = GIT_ECONFLICT;
+ goto cleanup;
+ }
+
+ /* Otherwise, stage any new files in the stash tree. (Note: their
+ * previously unstaged contents are staged, not the previously staged.)
+ */
+ } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) {
+ if ((error = stage_new_files(
+ &stash_adds, stash_parent_tree, stash_tree)) < 0 ||
+ (error = merge_indexes(
+ &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0)
+ goto cleanup;
+ }
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED);
+
+ /* Restore modified files in workdir */
+ if ((error = merge_index_and_tree(
+ &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0)
+ goto cleanup;
+
+ /* If applicable, restore untracked / ignored files in workdir */
+ if (untracked_tree) {
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED);
+
+ if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0)
+ goto cleanup;
+ }
+
+ if (untracked_index) {
+ opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED);
+
+ if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0)
+ goto cleanup;
+
+ opts.checkout_options.checkout_strategy = checkout_strategy;
+ }
+
+
+ /* If there are conflicts in the modified index, then we need to actually
+ * check that out as the repo's index. Otherwise, we don't update the
+ * index.
+ */
+
+ if (!git_index_has_conflicts(modified_index))
+ opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
+
+ /* Check out the modified index using the existing repo index as baseline,
+ * so that existing modifications in the index can be rewritten even when
+ * checking out safely.
+ */
+ opts.checkout_options.baseline_index = repo_index;
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED);
+
+ if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0)
+ goto cleanup;
+
+ if (unstashed_index && !git_index_has_conflicts(modified_index)) {
+ if ((error = git_index_read_index(repo_index, unstashed_index)) < 0)
+ goto cleanup;
+ }
+
+ NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE);
+
+ error = git_index_write(repo_index);
+
+cleanup:
+ git_index_free(untracked_index);
+ git_index_free(modified_index);
+ git_index_free(unstashed_index);
+ git_index_free(stash_adds);
+ git_index_free(repo_index);
+ git_tree_free(untracked_tree);
+ git_tree_free(index_parent_tree);
+ git_tree_free(index_tree);
+ git_tree_free(stash_parent_tree);
+ git_tree_free(stash_tree);
+ git_commit_free(stash_commit);
+ return error;
+}
+
+int git_stash_foreach(
+ git_repository *repo,
+ git_stash_cb callback,
+ void *payload)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t i, max;
+ const git_reflog_entry *entry;
+
+ error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ for (i = 0; i < max; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+
+ error = callback(i,
+ git_reflog_entry_message(entry),
+ git_reflog_entry_id_new(entry),
+ payload);
+
+ if (error) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+int git_stash_drop(
+ git_repository *repo,
+ size_t index)
+{
+ git_transaction *tx;
+ git_reference *stash = NULL;
+ git_reflog *reflog = NULL;
+ size_t max;
+ int error;
+
+ if ((error = git_transaction_new(&tx, repo)) < 0)
+ return error;
+
+ if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+
+ if (!max || index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ if ((error = git_reflog_drop(reflog, index, true)) < 0)
+ goto cleanup;
+
+ if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0)
+ goto cleanup;
+
+ if (max == 1) {
+ if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+ } else if (index == 0) {
+ const git_reflog_entry *entry;
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+ if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0)
+ goto cleanup;
+ }
+
+ error = git_transaction_commit(tx);
+
+cleanup:
+ git_reference_free(stash);
+ git_transaction_free(tx);
+ git_reflog_free(reflog);
+ return error;
+}
+
+int git_stash_pop(
+ git_repository *repo,
+ size_t index,
+ const git_stash_apply_options *options)
+{
+ int error;
+
+ if ((error = git_stash_apply(repo, index, options)) < 0)
+ return error;
+
+ return git_stash_drop(repo, index);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2.h"
+#include "fileops.h"
+#include "hash.h"
+#include "vector.h"
+#include "tree.h"
+#include "status.h"
+#include "git2/status.h"
+#include "repository.h"
+#include "ignore.h"
+#include "index.h"
+
+#include "git2/diff.h"
+#include "diff.h"
+#include "diff_generate.h"
+
+static unsigned int index_delta2status(const git_diff_delta *head2idx)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ switch (head2idx->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_COPIED:
+ st = GIT_STATUS_INDEX_NEW;
+ break;
+ case GIT_DELTA_DELETED:
+ st = GIT_STATUS_INDEX_DELETED;
+ break;
+ case GIT_DELTA_MODIFIED:
+ st = GIT_STATUS_INDEX_MODIFIED;
+ break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_INDEX_RENAMED;
+
+ if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id))
+ st |= GIT_STATUS_INDEX_MODIFIED;
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_INDEX_TYPECHANGE;
+ break;
+ case GIT_DELTA_CONFLICTED:
+ st = GIT_STATUS_CONFLICTED;
+ break;
+ default:
+ break;
+ }
+
+ return st;
+}
+
+static unsigned int workdir_delta2status(
+ git_diff *diff, git_diff_delta *idx2wd)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ switch (idx2wd->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_COPIED:
+ case GIT_DELTA_UNTRACKED:
+ st = GIT_STATUS_WT_NEW;
+ break;
+ case GIT_DELTA_UNREADABLE:
+ st = GIT_STATUS_WT_UNREADABLE;
+ break;
+ case GIT_DELTA_DELETED:
+ st = GIT_STATUS_WT_DELETED;
+ break;
+ case GIT_DELTA_MODIFIED:
+ st = GIT_STATUS_WT_MODIFIED;
+ break;
+ case GIT_DELTA_IGNORED:
+ st = GIT_STATUS_IGNORED;
+ break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_WT_RENAMED;
+
+ if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) {
+ /* if OIDs don't match, we might need to calculate them now to
+ * discern between RENAMED vs RENAMED+MODIFED
+ */
+ if (git_oid_iszero(&idx2wd->old_file.id) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ &idx2wd->old_file.id, diff, idx2wd->old_file.path,
+ idx2wd->old_file.mode, idx2wd->old_file.size))
+ idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (git_oid_iszero(&idx2wd->new_file.id) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ &idx2wd->new_file.id, diff, idx2wd->new_file.path,
+ idx2wd->new_file.mode, idx2wd->new_file.size))
+ idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id))
+ st |= GIT_STATUS_WT_MODIFIED;
+ }
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_WT_TYPECHANGE;
+ break;
+ case GIT_DELTA_CONFLICTED:
+ st = GIT_STATUS_CONFLICTED;
+ break;
+ default:
+ break;
+ }
+
+ return st;
+}
+
+static bool status_is_included(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
+{
+ if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
+ return 1;
+
+ /* if excluding submodules and this is a submodule everywhere */
+ if (head2idx) {
+ if (head2idx->status != GIT_DELTA_ADDED &&
+ head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (head2idx->status != GIT_DELTA_DELETED &&
+ head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ }
+ if (idx2wd) {
+ if (idx2wd->status != GIT_DELTA_ADDED &&
+ idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (idx2wd->status != GIT_DELTA_DELETED &&
+ idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ }
+
+ /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
+ return 0;
+}
+
+static git_status_t status_compute(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ if (head2idx)
+ st |= index_delta2status(head2idx);
+
+ if (idx2wd)
+ st |= workdir_delta2status(status->idx2wd, idx2wd);
+
+ return st;
+}
+
+static int status_collect(
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd,
+ void *payload)
+{
+ git_status_list *status = payload;
+ git_status_entry *status_entry;
+
+ if (!status_is_included(status, head2idx, idx2wd))
+ return 0;
+
+ status_entry = git__malloc(sizeof(git_status_entry));
+ GITERR_CHECK_ALLOC(status_entry);
+
+ status_entry->status = status_compute(status, head2idx, idx2wd);
+ status_entry->head_to_index = head2idx;
+ status_entry->index_to_workdir = idx2wd;
+
+ return git_vector_insert(&status->paired, status_entry);
+}
+
+GIT_INLINE(int) status_entry_cmp_base(
+ const void *a,
+ const void *b,
+ int (*strcomp)(const char *a, const char *b))
+{
+ const git_status_entry *entry_a = a;
+ const git_status_entry *entry_b = b;
+ const git_diff_delta *delta_a, *delta_b;
+
+ delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
+ entry_a->head_to_index;
+ delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
+ entry_b->head_to_index;
+
+ if (!delta_a && delta_b)
+ return -1;
+ if (delta_a && !delta_b)
+ return 1;
+ if (!delta_a && !delta_b)
+ return 0;
+
+ return strcomp(delta_a->new_file.path, delta_b->new_file.path);
+}
+
+static int status_entry_icmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcasecmp);
+}
+
+static int status_entry_cmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcmp);
+}
+
+static git_status_list *git_status_list_alloc(git_index *index)
+{
+ git_status_list *status = NULL;
+ int (*entrycmp)(const void *a, const void *b);
+
+ if (!(status = git__calloc(1, sizeof(git_status_list))))
+ return NULL;
+
+ entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
+
+ if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
+ git__free(status);
+ return NULL;
+ }
+
+ return status;
+}
+
+static int status_validate_options(const git_status_options *opts)
+{
+ if (!opts)
+ return 0;
+
+ GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
+
+ if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ giterr_set(GITERR_INVALID, "Unknown status 'show' option");
+ return -1;
+ }
+
+ if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 &&
+ (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) {
+ giterr_set(GITERR_INVALID, "Updating index from status "
+ "is not allowed when index refresh is disabled");
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_status_list_new(
+ git_status_list **out,
+ git_repository *repo,
+ const git_status_options *opts)
+{
+ git_index *index = NULL;
+ git_status_list *status = NULL;
+ git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT;
+ git_tree *head = NULL;
+ git_status_show_t show =
+ opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ int error = 0;
+ unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
+
+ *out = NULL;
+
+ if (status_validate_options(opts) < 0)
+ return -1;
+
+ if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0)
+ return error;
+
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ if ((error = git_repository_head_tree(&head, repo)) < 0) {
+ if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH)
+ goto done;
+ giterr_clear();
+ }
+
+ /* refresh index from disk unless prevented */
+ if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 &&
+ git_index_read(index, false) < 0)
+ giterr_clear();
+
+ status = git_status_list_alloc(index);
+ GITERR_CHECK_ALLOC(status);
+
+ if (opts) {
+ memcpy(&status->opts, opts, sizeof(git_status_options));
+ memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ }
+
+ diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
+ findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
+
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
+ if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
+ if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+ if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
+ findopt.flags = findopt.flags |
+ GIT_DIFF_FIND_AND_BREAK_REWRITES |
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES |
+ GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY;
+
+ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ if ((error = git_diff_tree_to_index(
+ &status->head2idx, repo, head, index, &diffopt)) < 0)
+ goto done;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
+ (error = git_diff_find_similar(status->head2idx, &findopt)) < 0)
+ goto done;
+ }
+
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
+ if ((error = git_diff_index_to_workdir(
+ &status->idx2wd, repo, index, &diffopt)) < 0) {
+ goto done;
+ }
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
+ (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
+ goto done;
+ }
+
+ error = git_diff__paired_foreach(
+ status->head2idx, status->idx2wd, status_collect, status);
+ if (error < 0)
+ goto done;
+
+ if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_cmp);
+ if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_icmp);
+
+ if ((flags &
+ (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
+ git_vector_sort(&status->paired);
+
+done:
+ if (error < 0) {
+ git_status_list_free(status);
+ status = NULL;
+ }
+
+ *out = status;
+
+ git_tree_free(head);
+ git_index_free(index);
+
+ return error;
+}
+
+size_t git_status_list_entrycount(git_status_list *status)
+{
+ assert(status);
+
+ return status->paired.length;
+}
+
+const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
+{
+ assert(status);
+
+ return git_vector_get(&status->paired, i);
+}
+
+void git_status_list_free(git_status_list *status)
+{
+ if (status == NULL)
+ return;
+
+ git_diff_free(status->head2idx);
+ git_diff_free(status->idx2wd);
+
+ git_vector_free_deep(&status->paired);
+
+ git__memzero(status, sizeof(*status));
+ git__free(status);
+}
+
+int git_status_foreach_ext(
+ git_repository *repo,
+ const git_status_options *opts,
+ git_status_cb cb,
+ void *payload)
+{
+ git_status_list *status;
+ const git_status_entry *status_entry;
+ size_t i;
+ int error = 0;
+
+ if ((error = git_status_list_new(&status, repo, opts)) < 0) {
+ return error;
+ }
+
+ git_vector_foreach(&status->paired, i, status_entry) {
+ const char *path = status_entry->head_to_index ?
+ status_entry->head_to_index->old_file.path :
+ status_entry->index_to_workdir->old_file.path;
+
+ if ((error = cb(path, status_entry->status, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ git_status_list_free(status);
+
+ return error;
+}
+
+int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
+{
+ return git_status_foreach_ext(repo, NULL, cb, payload);
+}
+
+struct status_file_info {
+ char *expected;
+ unsigned int count;
+ unsigned int status;
+ int fnm_flags;
+ int ambiguous;
+};
+
+static int get_one_status(const char *path, unsigned int status, void *data)
+{
+ struct status_file_info *sfi = data;
+ int (*strcomp)(const char *a, const char *b);
+
+ sfi->count++;
+ sfi->status = status;
+
+ strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;
+
+ if (sfi->count > 1 ||
+ (strcomp(sfi->expected, path) != 0 &&
+ p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
+ {
+ sfi->ambiguous = true;
+ return GIT_EAMBIGUOUS; /* giterr_set will be done by caller */
+ }
+
+ return 0;
+}
+
+int git_status_file(
+ unsigned int *status_flags,
+ git_repository *repo,
+ const char *path)
+{
+ int error;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_file_info sfi = {0};
+ git_index *index;
+
+ assert(status_flags && repo && path);
+
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
+
+ if ((sfi.expected = git__strdup(path)) == NULL)
+ return -1;
+ if (index->ignore_case)
+ sfi.fnm_flags = FNM_CASEFOLD;
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &sfi.expected;
+
+ error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
+
+ if (error < 0 && sfi.ambiguous) {
+ giterr_set(GITERR_INVALID,
+ "Ambiguous path '%s' given to git_status_file", sfi.expected);
+ error = GIT_EAMBIGUOUS;
+ }
+
+ if (!error && !sfi.count) {
+ giterr_set(GITERR_INVALID,
+ "Attempt to get status of nonexistent file '%s'", path);
+ error = GIT_ENOTFOUND;
+ }
+
+ *status_flags = sfi.status;
+
+ git__free(sfi.expected);
+
+ return error;
+}
+
+int git_status_should_ignore(
+ int *ignored,
+ git_repository *repo,
+ const char *path)
+{
+ return git_ignore_path_is_ignored(ignored, repo, path);
+}
+
+int git_status_init_options(git_status_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT);
+ return 0;
+}
+
+int git_status_list_get_perfdata(
+ git_diff_perfdata *out, const git_status_list *status)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
+
+ out->stat_calls = 0;
+ out->oid_calculations = 0;
+
+ if (status->head2idx) {
+ out->stat_calls += status->head2idx->perf.stat_calls;
+ out->oid_calculations += status->head2idx->perf.oid_calculations;
+ }
+ if (status->idx2wd) {
+ out->stat_calls += status->idx2wd->perf.stat_calls;
+ out->oid_calculations += status->idx2wd->perf.oid_calculations;
+ }
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_status_h__
+#define INCLUDE_status_h__
+
+#include "diff.h"
+#include "git2/status.h"
+#include "git2/diff.h"
+
+struct git_status_list {
+ git_status_options opts;
+
+ git_diff *head2idx;
+ git_diff *idx2wd;
+
+ git_vector paired;
+};
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_SECURE_TRANSPORT
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/SecureTransport.h>
+#include <Security/SecCertificate.h>
+
+#include "git2/transport.h"
+
+#include "socket_stream.h"
+#include "curl_stream.h"
+
+static int stransport_error(OSStatus ret)
+{
+ CFStringRef message;
+
+ if (ret == noErr || ret == errSSLClosedGraceful) {
+ giterr_clear();
+ return 0;
+ }
+
+#if !TARGET_OS_IPHONE
+ message = SecCopyErrorMessageString(ret, NULL);
+ GITERR_CHECK_ALLOC(message);
+
+ giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
+ CFRelease(message);
+#else
+ giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
+ GIT_UNUSED(message);
+#endif
+
+ return -1;
+}
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ SSLContextRef ctx;
+ CFDataRef der_data;
+ git_cert_x509 cert_info;
+} stransport_stream;
+
+static int stransport_connect(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ int error;
+ SecTrustRef trust = NULL;
+ SecTrustResultType sec_res;
+ OSStatus ret;
+
+ if ((error = git_stream_connect(st->io)) < 0)
+ return error;
+
+ ret = SSLHandshake(st->ctx);
+ if (ret != errSSLServerAuthCompleted) {
+ giterr_set(GITERR_SSL, "unexpected return value from ssl handshake %d", ret);
+ return -1;
+ }
+
+ if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
+ goto on_error;
+
+ if (!trust)
+ return GIT_ECERTIFICATE;
+
+ if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
+ goto on_error;
+
+ CFRelease(trust);
+
+ if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
+ giterr_set(GITERR_SSL, "internal security trust error");
+ return -1;
+ }
+
+ if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
+ sec_res == kSecTrustResultFatalTrustFailure)
+ return GIT_ECERTIFICATE;
+
+ return 0;
+
+on_error:
+ if (trust)
+ CFRelease(trust);
+
+ return stransport_error(ret);
+}
+
+static int stransport_certificate(git_cert **out, git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ SecTrustRef trust = NULL;
+ SecCertificateRef sec_cert;
+ OSStatus ret;
+
+ if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
+ return stransport_error(ret);
+
+ sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
+ st->der_data = SecCertificateCopyData(sec_cert);
+ CFRelease(trust);
+
+ if (st->der_data == NULL) {
+ giterr_set(GITERR_SSL, "retrieved invalid certificate data");
+ return -1;
+ }
+
+ st->cert_info.parent.cert_type = GIT_CERT_X509;
+ st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data);
+ st->cert_info.len = CFDataGetLength(st->der_data);
+
+ *out = (git_cert *)&st->cert_info;
+ return 0;
+}
+
+static int stransport_set_proxy(
+ git_stream *stream,
+ const git_proxy_options *proxy_opts)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_opts);
+}
+
+/*
+ * Contrary to typical network IO callbacks, Secure Transport write callback is
+ * expected to write *all* passed data, not just as much as it can, and any
+ * other case would be considered a failure.
+ *
+ * This behavior is actually not specified in the Apple documentation, but is
+ * required for things to work correctly (and incidentally, that's also how
+ * Apple implements it in its projects at opensource.apple.com).
+ *
+ * Libgit2 streams happen to already have this very behavior so this is just
+ * passthrough.
+ */
+static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ git_stream *io = (git_stream *) conn;
+
+ if (git_stream_write(io, data, *len, 0) < 0) {
+ return -36; /* "ioErr" from MacErrors.h which is not available on iOS */
+ }
+
+ return noErr;
+}
+
+static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ size_t data_len, processed;
+ OSStatus ret;
+
+ GIT_UNUSED(flags);
+
+ data_len = len;
+ if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr)
+ return stransport_error(ret);
+
+ return processed;
+}
+
+/*
+ * Contrary to typical network IO callbacks, Secure Transport read callback is
+ * expected to read *exactly* the requested number of bytes, not just as much
+ * as it can, and any other case would be considered a failure.
+ *
+ * This behavior is actually not specified in the Apple documentation, but is
+ * required for things to work correctly (and incidentally, that's also how
+ * Apple implements it in its projects at opensource.apple.com).
+ */
+static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len)
+{
+ git_stream *io = (git_stream *) conn;
+ OSStatus error = noErr;
+ size_t off = 0;
+ ssize_t ret;
+
+ do {
+ ret = git_stream_read(io, data + off, *len - off);
+ if (ret < 0) {
+ error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */
+ break;
+ }
+ if (ret == 0) {
+ error = errSSLClosedGraceful;
+ break;
+ }
+
+ off += ret;
+ } while (off < *len);
+
+ *len = off;
+ return error;
+}
+
+static ssize_t stransport_read(git_stream *stream, void *data, size_t len)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ size_t processed;
+ OSStatus ret;
+
+ if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr)
+ return stransport_error(ret);
+
+ return processed;
+}
+
+static int stransport_close(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ OSStatus ret;
+
+ ret = SSLClose(st->ctx);
+ if (ret != noErr && ret != errSSLClosedGraceful)
+ return stransport_error(ret);
+
+ return git_stream_close(st->io);
+}
+
+static void stransport_free(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+
+ git_stream_free(st->io);
+ CFRelease(st->ctx);
+ if (st->der_data)
+ CFRelease(st->der_data);
+ git__free(st);
+}
+
+int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
+{
+ stransport_stream *st;
+ int error;
+ OSStatus ret;
+
+ assert(out && host);
+
+ st = git__calloc(1, sizeof(stransport_stream));
+ GITERR_CHECK_ALLOC(st);
+
+#ifdef GIT_CURL
+ error = git_curl_stream_new(&st->io, host, port);
+#else
+ error = git_socket_stream_new(&st->io, host, port);
+#endif
+
+ if (error < 0){
+ git__free(st);
+ return error;
+ }
+
+ st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!st->ctx) {
+ giterr_set(GITERR_NET, "failed to create SSL context");
+ git__free(st);
+ return -1;
+ }
+
+ if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr ||
+ (ret = SSLSetConnection(st->ctx, st->io)) != noErr ||
+ (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr ||
+ (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr ||
+ (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr ||
+ (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) {
+ CFRelease(st->ctx);
+ git__free(st);
+ return stransport_error(ret);
+ }
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
+ st->parent.connect = stransport_connect;
+ st->parent.certificate = stransport_certificate;
+ st->parent.set_proxy = stransport_set_proxy;
+ st->parent.read = stransport_read;
+ st->parent.write = stransport_write;
+ st->parent.close = stransport_close;
+ st->parent.free = stransport_free;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_stransport_stream_h__
+#define INCLUDE_stransport_stream_h__
+
+#include "git2/sys/stream.h"
+
+extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_stream_h__
+#define INCLUDE_stream_h__
+
+#include "common.h"
+#include "git2/sys/stream.h"
+
+GIT_INLINE(int) git_stream_connect(git_stream *st)
+{
+ return st->connect(st);
+}
+
+GIT_INLINE(int) git_stream_is_encrypted(git_stream *st)
+{
+ return st->encrypted;
+}
+
+GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st)
+{
+ if (!st->encrypted) {
+ giterr_set(GITERR_INVALID, "an unencrypted stream does not have a certificate");
+ return -1;
+ }
+
+ return st->certificate(out, st);
+}
+
+GIT_INLINE(int) git_stream_supports_proxy(git_stream *st)
+{
+ return st->proxy_support;
+}
+
+GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts)
+{
+ if (!st->proxy_support) {
+ giterr_set(GITERR_INVALID, "proxy not supported on this stream");
+ return -1;
+ }
+
+ return st->set_proxy(st, proxy_opts);
+}
+
+GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len)
+{
+ return st->read(st, data, len);
+}
+
+GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags)
+{
+ return st->write(st, data, len, flags);
+}
+
+GIT_INLINE(int) git_stream_close(git_stream *st)
+{
+ return st->close(st);
+}
+
+GIT_INLINE(void) git_stream_free(git_stream *st)
+{
+ if (!st)
+ return;
+
+ st->free(st);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "strmap.h"
+
+int git_strmap_next(
+ void **data,
+ git_strmap_iter* iter,
+ git_strmap *map)
+{
+ if (!map)
+ return GIT_ERROR;
+
+ while (*iter != git_strmap_end(map)) {
+ if (!(git_strmap_has_data(map, *iter))) {
+ ++(*iter);
+ continue;
+ }
+
+ *data = git_strmap_value_at(map, *iter);
+
+ ++(*iter);
+
+ return GIT_OK;
+ }
+
+ return GIT_ITEROVER;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_strmap_h__
+#define INCLUDE_strmap_h__
+
+#include "common.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kreallocarray git__reallocarray
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(str, const char *, void *)
+typedef khash_t(str) git_strmap;
+typedef khiter_t git_strmap_iter;
+
+#define GIT__USE_STRMAP \
+ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#define git_strmap_alloc(hp) \
+ ((*(hp) = kh_init(str)) == NULL) ? giterr_set_oom(), -1 : 0
+
+#define git_strmap_free(h) kh_destroy(str, h), h = NULL
+#define git_strmap_clear(h) kh_clear(str, h)
+
+#define git_strmap_num_entries(h) kh_size(h)
+
+#define git_strmap_lookup_index(h, k) kh_get(str, h, k)
+#define git_strmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_strmap_exists(h, k) (kh_get(str, h, k) != kh_end(h))
+#define git_strmap_has_data(h, idx) kh_exist(h, idx)
+
+#define git_strmap_key(h, idx) kh_key(h, idx)
+#define git_strmap_value_at(h, idx) kh_val(h, idx)
+#define git_strmap_set_value_at(h, idx, v) kh_val(h, idx) = v
+#define git_strmap_delete_at(h, idx) kh_del(str, h, idx)
+
+#define git_strmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(str, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_strmap_insert2(h, key, val, oldv, rval) do { \
+ khiter_t __pos = kh_put(str, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) { \
+ oldv = kh_val(h, __pos); \
+ kh_key(h, __pos) = key; \
+ } else { oldv = NULL; } \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_strmap_delete(h, key) do { \
+ khiter_t __pos = git_strmap_lookup_index(h, key); \
+ if (git_strmap_valid_index(h, __pos)) \
+ git_strmap_delete_at(h, __pos); } while (0)
+
+#define git_strmap_foreach kh_foreach
+#define git_strmap_foreach_value kh_foreach_value
+
+#define git_strmap_begin kh_begin
+#define git_strmap_end kh_end
+
+int git_strmap_next(
+ void **data,
+ git_strmap_iter* iter,
+ git_strmap *map);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_strlen_h__
+#define INCLUDE_strlen_h__
+
+#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\
+ (defined(_MSC_VER) && _MSC_VER < 1500)
+# define NO_STRNLEN
+#endif
+
+#ifdef NO_STRNLEN
+GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) {
+ const char *end = memchr(s, 0, maxlen);
+ return end ? (size_t)(end - s) : maxlen;
+}
+#else
+# define p_strnlen strnlen
+#endif
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/config.h"
+#include "git2/sys/config.h"
+#include "git2/types.h"
+#include "git2/index.h"
+#include "buffer.h"
+#include "buf_text.h"
+#include "vector.h"
+#include "posix.h"
+#include "config_file.h"
+#include "config.h"
+#include "repository.h"
+#include "submodule.h"
+#include "tree.h"
+#include "iterator.h"
+#include "path.h"
+#include "index.h"
+
+#define GIT_MODULES_FILE ".gitmodules"
+
+static git_cvar_map _sm_update_map[] = {
+ {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
+ {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
+ {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT},
+};
+
+static git_cvar_map _sm_ignore_map[] = {
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
+ {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
+ {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
+ {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL},
+};
+
+static git_cvar_map _sm_recurse_map[] = {
+ {GIT_CVAR_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES},
+};
+
+enum {
+ CACHE_OK = 0,
+ CACHE_REFRESH = 1,
+ CACHE_FLUSH = 2
+};
+enum {
+ GITMODULES_EXISTING = 0,
+ GITMODULES_CREATE = 1,
+};
+
+static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
+{
+ khint_t h;
+
+ for (h = 0; *s; ++s)
+ if (s[1] != '\0' || *s != '/')
+ h = (h << 5) - h + *s;
+
+ return h;
+}
+
+static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
+{
+ size_t alen = a ? strlen(a) : 0;
+ size_t blen = b ? strlen(b) : 0;
+
+ if (alen > 0 && a[alen - 1] == '/')
+ alen--;
+ if (blen > 0 && b[blen - 1] == '/')
+ blen--;
+
+ return (alen == 0 && blen == 0) ||
+ (alen == blen && strncmp(a, b, alen) == 0);
+}
+
+__KHASH_IMPL(
+ str, static kh_inline, const char *, void *, 1,
+ str_hash_no_trailing_slash, str_equal_no_trailing_slash)
+
+static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name);
+static git_config_backend *open_gitmodules(git_repository *repo, int gitmod);
+static git_config *gitmodules_snapshot(git_repository *repo);
+static int get_url_base(git_buf *url, git_repository *repo);
+static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo);
+static int lookup_default_remote(git_remote **remote, git_repository *repo);
+static int submodule_load_each(const git_config_entry *entry, void *payload);
+static int submodule_read_config(git_submodule *sm, git_config *cfg);
+static int submodule_load_from_wd_lite(git_submodule *);
+static void submodule_get_index_status(unsigned int *, git_submodule *);
+static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t);
+static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie);
+static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id);
+
+static int submodule_cmp(const void *a, const void *b)
+{
+ return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+}
+
+static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
+{
+ ssize_t idx = git_buf_rfind(key, '.');
+ git_buf_truncate(key, (size_t)(idx + 1));
+ return git_buf_puts(key, suffix);
+}
+
+/*
+ * PUBLIC APIS
+ */
+
+static void submodule_set_lookup_error(int error, const char *name)
+{
+ if (!error)
+ return;
+
+ giterr_set(GITERR_SUBMODULE, (error == GIT_ENOTFOUND) ?
+ "No submodule named '%s'" :
+ "Submodule '%s' has not been added yet", name);
+}
+
+typedef struct {
+ const char *path;
+ char *name;
+} fbp_data;
+
+static int find_by_path(const git_config_entry *entry, void *payload)
+{
+ fbp_data *data = payload;
+
+ if (!strcmp(entry->value, data->path)) {
+ const char *fdot, *ldot;
+ fdot = strchr(entry->name, '.');
+ ldot = strrchr(entry->name, '.');
+ data->name = git__strndup(fdot + 1, ldot - fdot - 1);
+ GITERR_CHECK_ALLOC(data->name);
+ }
+
+ return 0;
+}
+
+/**
+ * Find out the name of a submodule from its path
+ */
+static int name_from_path(git_buf *out, git_config *cfg, const char *path)
+{
+ const char *key = "submodule\\..*\\.path";
+ git_config_iterator *iter;
+ git_config_entry *entry;
+ int error;
+
+ if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
+ return error;
+
+ while ((error = git_config_next(&entry, iter)) == 0) {
+ const char *fdot, *ldot;
+ /* TODO: this should maybe be strcasecmp on a case-insensitive fs */
+ if (strcmp(path, entry->value) != 0)
+ continue;
+
+ fdot = strchr(entry->name, '.');
+ ldot = strrchr(entry->name, '.');
+
+ git_buf_clear(out);
+ git_buf_put(out, fdot + 1, ldot - fdot - 1);
+ goto cleanup;
+ }
+
+ if (error == GIT_ITEROVER) {
+ giterr_set(GITERR_SUBMODULE, "could not find a submodule name for '%s'", path);
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ git_config_iterator_free(iter);
+ return error;
+}
+
+int git_submodule_lookup(
+ git_submodule **out, /* NULL if user only wants to test existence */
+ git_repository *repo,
+ const char *name) /* trailing slash is allowed */
+{
+ int error;
+ unsigned int location;
+ git_submodule *sm;
+
+ assert(repo && name);
+
+ if ((error = submodule_alloc(&sm, repo, name)) < 0)
+ return error;
+
+ if ((error = git_submodule_reload(sm, false)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ if ((error = git_submodule_location(&location, sm)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ /* If it's not configured or we're looking by path */
+ if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
+ git_config_backend *mods;
+ const char *pattern = "submodule\\..*\\.path";
+ git_buf path = GIT_BUF_INIT;
+ fbp_data data = { NULL, NULL };
+
+ git_buf_puts(&path, name);
+ while (path.ptr[path.size-1] == '/') {
+ path.ptr[--path.size] = '\0';
+ }
+ data.path = path.ptr;
+
+ mods = open_gitmodules(repo, GITMODULES_EXISTING);
+
+ if (mods)
+ error = git_config_file_foreach_match(mods, pattern, find_by_path, &data);
+
+ git_config_file_free(mods);
+
+ if (error < 0) {
+ git_submodule_free(sm);
+ git_buf_free(&path);
+ return error;
+ }
+
+ if (data.name) {
+ git__free(sm->name);
+ sm->name = data.name;
+ sm->path = git_buf_detach(&path);
+
+ /* Try to load again with the right name */
+ if ((error = git_submodule_reload(sm, false)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+ }
+
+ git_buf_free(&path);
+ }
+
+ if ((error = git_submodule_location(&location, sm)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ /* If we still haven't found it, do the WD check */
+ if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
+ git_submodule_free(sm);
+ error = GIT_ENOTFOUND;
+
+ /* If it's not configured, we still check if there's a repo at the path */
+ if (git_repository_workdir(repo)) {
+ git_buf path = GIT_BUF_INIT;
+ if (git_buf_join3(&path,
+ '/', git_repository_workdir(repo), name, DOT_GIT) < 0)
+ return -1;
+
+ if (git_path_exists(path.ptr))
+ error = GIT_EEXISTS;
+
+ git_buf_free(&path);
+ }
+
+ submodule_set_lookup_error(error, name);
+ return error;
+ }
+
+ if (out)
+ *out = sm;
+ else
+ git_submodule_free(sm);
+
+ return 0;
+}
+
+static void submodule_free_dup(void *sm)
+{
+ git_submodule_free(sm);
+}
+
+static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name)
+{
+ int error = 0;
+ khiter_t pos;
+ git_submodule *sm = NULL;
+
+ pos = git_strmap_lookup_index(map, name);
+ if (git_strmap_valid_index(map, pos)) {
+ sm = git_strmap_value_at(map, pos);
+ goto done;
+ }
+
+ /* if the submodule doesn't exist yet in the map, create it */
+ if ((error = submodule_alloc(&sm, repo, name)) < 0)
+ return error;
+
+ pos = kh_put(str, map, sm->name, &error);
+ /* nobody can beat us to adding it */
+ assert(error != 0);
+ if (error < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ git_strmap_set_value_at(map, pos, sm);
+
+done:
+ GIT_REFCOUNT_INC(sm);
+ *out = sm;
+ return 0;
+}
+
+static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg)
+{
+ int error;
+ git_iterator *i;
+ const git_index_entry *entry;
+ git_buf name = GIT_BUF_INIT;
+
+ if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
+ return error;
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(map, entry->path);
+ git_submodule *sm;
+
+ git_buf_clear(&name);
+ if (!name_from_path(&name, cfg, entry->path)) {
+ git_strmap_lookup_index(map, name.ptr);
+ }
+
+ if (git_strmap_valid_index(map, pos)) {
+ sm = git_strmap_value_at(map, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_index_entry(sm, entry);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name.ptr ? name.ptr : entry->path)) {
+ submodule_update_from_index_entry(sm, entry);
+ git_submodule_free(sm);
+ }
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_buf_free(&name);
+ git_iterator_free(i);
+
+ return error;
+}
+
+static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg)
+{
+ int error;
+ git_iterator *i;
+ const git_index_entry *entry;
+ git_buf name = GIT_BUF_INIT;
+
+ if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
+ return error;
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(map, entry->path);
+ git_submodule *sm;
+
+ git_buf_clear(&name);
+ if (!name_from_path(&name, cfg, entry->path)) {
+ git_strmap_lookup_index(map, name.ptr);
+ }
+
+ if (git_strmap_valid_index(map, pos)) {
+ sm = git_strmap_value_at(map, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_head_data(sm, entry->mode, &entry->id);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name.ptr ? name.ptr : entry->path)) {
+ submodule_update_from_head_data(
+ sm, entry->mode, &entry->id);
+ git_submodule_free(sm);
+ }
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_buf_free(&name);
+ git_iterator_free(i);
+
+ return error;
+}
+
+/* If have_sm is true, sm is populated, otherwise map an repo are. */
+typedef struct {
+ git_config *mods;
+ git_strmap *map;
+ git_repository *repo;
+} lfc_data;
+
+static int all_submodules(git_repository *repo, git_strmap *map)
+{
+ int error = 0;
+ git_index *idx = NULL;
+ git_tree *head = NULL;
+ const char *wd = NULL;
+ git_buf path = GIT_BUF_INIT;
+ git_submodule *sm;
+ git_config *mods = NULL;
+ uint32_t mask;
+
+ assert(repo && map);
+
+ /* get sources that we will need to check */
+ if (git_repository_index(&idx, repo) < 0)
+ giterr_clear();
+ if (git_repository_head_tree(&head, repo) < 0)
+ giterr_clear();
+
+ wd = git_repository_workdir(repo);
+ if (wd && (error = git_buf_joinpath(&path, wd, GIT_MODULES_FILE)) < 0)
+ goto cleanup;
+
+ /* clear submodule flags that are to be refreshed */
+ mask = 0;
+ mask |= GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_FLAGS |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID |
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+
+ mask |= GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+ mask |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+ if (mask != 0)
+ mask |= GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_SCANNED |
+ GIT_SUBMODULE_STATUS__WD_FLAGS |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID;
+
+ /* add submodule information from .gitmodules */
+ if (wd) {
+ lfc_data data = { 0 };
+ data.map = map;
+ data.repo = repo;
+
+ if ((mods = gitmodules_snapshot(repo)) == NULL)
+ goto cleanup;
+
+ data.mods = mods;
+ if ((error = git_config_foreach(
+ mods, submodule_load_each, &data)) < 0)
+ goto cleanup;
+ }
+ /* add back submodule information from index */
+ if (idx) {
+ if ((error = submodules_from_index(map, idx, mods)) < 0)
+ goto cleanup;
+ }
+ /* add submodule information from HEAD */
+ if (head) {
+ if ((error = submodules_from_head(map, head, mods)) < 0)
+ goto cleanup;
+ }
+ /* shallow scan submodules in work tree as needed */
+ if (wd && mask != 0) {
+ git_strmap_foreach_value(map, sm, {
+ submodule_load_from_wd_lite(sm);
+ });
+ }
+
+cleanup:
+ git_config_free(mods);
+ /* TODO: if we got an error, mark submodule config as invalid? */
+ git_index_free(idx);
+ git_tree_free(head);
+ git_buf_free(&path);
+ return error;
+}
+
+int git_submodule_foreach(
+ git_repository *repo,
+ git_submodule_cb callback,
+ void *payload)
+{
+ git_vector snapshot = GIT_VECTOR_INIT;
+ git_strmap *submodules;
+ git_submodule *sm;
+ int error;
+ size_t i;
+
+ if ((error = git_strmap_alloc(&submodules)) < 0)
+ return error;
+
+ if ((error = all_submodules(repo, submodules)) < 0)
+ goto done;
+
+ if (!(error = git_vector_init(
+ &snapshot, kh_size(submodules), submodule_cmp))) {
+
+ git_strmap_foreach_value(submodules, sm, {
+ if ((error = git_vector_insert(&snapshot, sm)) < 0)
+ break;
+ GIT_REFCOUNT_INC(sm);
+ });
+ }
+
+ if (error < 0)
+ goto done;
+
+ git_vector_uniq(&snapshot, submodule_free_dup);
+
+ git_vector_foreach(&snapshot, i, sm) {
+ if ((error = callback(sm, sm->name, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+done:
+ git_vector_foreach(&snapshot, i, sm)
+ git_submodule_free(sm);
+ git_vector_free(&snapshot);
+
+ git_strmap_foreach_value(submodules, sm, {
+ git_submodule_free(sm);
+ });
+ git_strmap_free(submodules);
+
+ return error;
+}
+
+static int submodule_repo_init(
+ git_repository **out,
+ git_repository *parent_repo,
+ const char *path,
+ const char *url,
+ bool use_gitlink)
+{
+ int error = 0;
+ git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ git_repository *subrepo = NULL;
+
+ error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
+ initopt.origin_url = url;
+
+ /* init submodule repository and add origin remote as needed */
+
+ /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
+ * gitlink in the sub-repo workdir directory to that repository
+ *
+ * Old style: sub-repo goes directly into repo/<name>/.git/
+ */
+ if (use_gitlink) {
+ error = git_buf_join3(
+ &repodir, '/', git_repository_path(parent_repo), "modules", path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.workdir_path = workdir.ptr;
+ initopt.flags |=
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
+ GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
+
+ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+ } else
+ error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt);
+
+cleanup:
+ git_buf_free(&workdir);
+ git_buf_free(&repodir);
+
+ *out = subrepo;
+
+ return error;
+}
+
+int git_submodule_add_setup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *url,
+ const char *path,
+ int use_gitlink)
+{
+ int error = 0;
+ git_config_backend *mods = NULL;
+ git_submodule *sm = NULL;
+ git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
+ git_repository *subrepo = NULL;
+
+ assert(repo && url && path);
+
+ /* see if there is already an entry for this submodule */
+
+ if (git_submodule_lookup(NULL, repo, path) < 0)
+ giterr_clear();
+ else {
+ giterr_set(GITERR_SUBMODULE,
+ "Attempt to add submodule '%s' that already exists", path);
+ return GIT_EEXISTS;
+ }
+
+ /* validate and normalize path */
+
+ if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
+ path += strlen(git_repository_workdir(repo));
+
+ if (git_path_root(path) >= 0) {
+ giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");
+ error = -1;
+ goto cleanup;
+ }
+
+ /* update .gitmodules */
+
+ if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) {
+ giterr_set(GITERR_SUBMODULE,
+ "Adding submodules to a bare repository is not supported");
+ return -1;
+ }
+
+ if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, path)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, url)) < 0)
+ goto cleanup;
+
+ git_buf_clear(&name);
+
+ /* init submodule repository and add origin remote as needed */
+
+ error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ /* if the repo does not already exist, then init a new repo and add it.
+ * Otherwise, just add the existing repo.
+ */
+ if (!(git_path_exists(name.ptr) &&
+ git_path_contains(&name, DOT_GIT))) {
+
+ /* resolve the actual URL to use */
+ if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_submodule_lookup(&sm, repo, path)) < 0)
+ goto cleanup;
+
+ error = git_submodule_init(sm, false);
+
+cleanup:
+ if (error && sm) {
+ git_submodule_free(sm);
+ sm = NULL;
+ }
+ if (out != NULL)
+ *out = sm;
+
+ git_config_file_free(mods);
+ git_repository_free(subrepo);
+ git_buf_free(&real_url);
+ git_buf_free(&name);
+
+ return error;
+}
+
+int git_submodule_repo_init(
+ git_repository **out,
+ const git_submodule *sm,
+ int use_gitlink)
+{
+ int error;
+ git_repository *sub_repo = NULL;
+ const char *configured_url;
+ git_config *cfg = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(out && sm);
+
+ /* get the configured remote url of the submodule */
+ if ((error = git_buf_printf(&buf, "submodule.%s.url", sm->name)) < 0 ||
+ (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 ||
+ (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 ||
+ (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0)
+ goto done;
+
+ *out = sub_repo;
+
+done:
+ git_config_free(cfg);
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_submodule_add_finalize(git_submodule *sm)
+{
+ int error;
+ git_index *index;
+
+ assert(sm);
+
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+ (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
+ return error;
+
+ return git_submodule_add_to_index(sm, true);
+}
+
+int git_submodule_add_to_index(git_submodule *sm, int write_index)
+{
+ int error;
+ git_repository *sm_repo = NULL;
+ git_index *index;
+ git_buf path = GIT_BUF_INIT;
+ git_commit *head;
+ git_index_entry entry;
+ struct stat st;
+
+ assert(sm);
+
+ /* force reload of wd OID by git_submodule_open */
+ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
+
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+ (error = git_buf_joinpath(
+ &path, git_repository_workdir(sm->repo), sm->path)) < 0 ||
+ (error = git_submodule_open(&sm_repo, sm)) < 0)
+ goto cleanup;
+
+ /* read stat information for submodule working directory */
+ if (p_stat(path.ptr, &st) < 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without working directory");
+ error = -1;
+ goto cleanup;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = sm->path;
+ git_index_entry__init_from_stat(
+ &entry, &st, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE));
+
+ /* calling git_submodule_open will have set sm->wd_oid if possible */
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without HEAD to index");
+ error = -1;
+ goto cleanup;
+ }
+ git_oid_cpy(&entry.id, &sm->wd_oid);
+
+ if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
+ goto cleanup;
+
+ entry.ctime.seconds = (int32_t)git_commit_time(head);
+ entry.ctime.nanoseconds = 0;
+ entry.mtime.seconds = (int32_t)git_commit_time(head);
+ entry.mtime.nanoseconds = 0;
+
+ git_commit_free(head);
+
+ /* add it */
+ error = git_index_add(index, &entry);
+
+ /* write it, if requested */
+ if (!error && write_index) {
+ error = git_index_write(index);
+
+ if (!error)
+ git_oid_cpy(&sm->index_oid, &sm->wd_oid);
+ }
+
+cleanup:
+ git_repository_free(sm_repo);
+ git_buf_free(&path);
+ return error;
+}
+
+const char *git_submodule_update_to_str(git_submodule_update_t update)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i)
+ if (_sm_update_map[i].map_value == (int)update)
+ return _sm_update_map[i].str_match;
+ return NULL;
+}
+
+git_repository *git_submodule_owner(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->repo;
+}
+
+const char *git_submodule_name(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->name;
+}
+
+const char *git_submodule_path(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->path;
+}
+
+const char *git_submodule_url(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->url;
+}
+
+int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *url)
+{
+ int error = 0;
+ git_buf normalized = GIT_BUF_INIT;
+
+ assert(out && repo && url);
+
+ git_buf_sanitize(out);
+
+ /* We do this in all platforms in case someone on Windows created the .gitmodules */
+ if (strchr(url, '\\')) {
+ if ((error = git_path_normalize_slashes(&normalized, url)) < 0)
+ return error;
+
+ url = normalized.ptr;
+ }
+
+
+ if (git_path_is_relative(url)) {
+ if (!(error = get_url_base(out, repo)))
+ error = git_path_apply_relative(out, url);
+ } else if (strchr(url, ':') != NULL || url[0] == '/') {
+ error = git_buf_sets(out, url);
+ } else {
+ giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
+ error = -1;
+ }
+
+ git_buf_free(&normalized);
+ return error;
+}
+
+static int write_var(git_repository *repo, const char *name, const char *var, const char *val)
+{
+ git_buf key = GIT_BUF_INIT;
+ git_config_backend *mods;
+ int error;
+
+ mods = open_gitmodules(repo, GITMODULES_CREATE);
+ if (!mods)
+ return -1;
+
+ if ((error = git_buf_printf(&key, "submodule.%s.%s", name, var)) < 0)
+ goto cleanup;
+
+ if (val)
+ error = git_config_file_set_string(mods, key.ptr, val);
+ else
+ error = git_config_file_delete(mods, key.ptr);
+
+ git_buf_free(&key);
+
+cleanup:
+ git_config_file_free(mods);
+ return error;
+}
+
+static int write_mapped_var(git_repository *repo, const char *name, git_cvar_map *maps, size_t nmaps, const char *var, int ival)
+{
+ git_cvar_t type;
+ const char *val;
+
+ if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) {
+ giterr_set(GITERR_SUBMODULE, "invalid value for %s", var);
+ return -1;
+ }
+
+ if (type == GIT_CVAR_TRUE)
+ val = "true";
+
+ return write_var(repo, name, var, val);
+}
+
+const char *git_submodule_branch(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->branch;
+}
+
+int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch)
+{
+
+ assert(repo && name);
+
+ return write_var(repo, name, "branch", branch);
+}
+
+int git_submodule_set_url(git_repository *repo, const char *name, const char *url)
+{
+ assert(repo && name && url);
+
+ return write_var(repo, name, "url", url);
+}
+
+const git_oid *git_submodule_index_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
+ return &submodule->index_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_head_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
+ return &submodule->head_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_wd_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ /* load unless we think we have a valid oid */
+ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
+ git_repository *subrepo;
+
+ /* calling submodule open grabs the HEAD OID if possible */
+ if (!git_submodule_open_bare(&subrepo, submodule))
+ git_repository_free(subrepo);
+ else
+ giterr_clear();
+ }
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
+ return &submodule->wd_oid;
+ else
+ return NULL;
+}
+
+git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
+{
+ assert(submodule);
+ return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ?
+ GIT_SUBMODULE_IGNORE_NONE : submodule->ignore;
+}
+
+int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore)
+{
+ assert(repo && name);
+
+ return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore);
+}
+
+git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule)
+{
+ assert(submodule);
+ return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update;
+}
+
+int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update)
+{
+ assert(repo && name);
+
+ return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update);
+}
+
+git_submodule_recurse_t git_submodule_fetch_recurse_submodules(
+ git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->fetch_recurse;
+}
+
+int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse)
+{
+ assert(repo && name);
+
+ return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse);
+}
+
+static int submodule_repo_create(
+ git_repository **out,
+ git_repository *parent_repo,
+ const char *path)
+{
+ int error = 0;
+ git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ git_repository *subrepo = NULL;
+
+ initopt.flags =
+ GIT_REPOSITORY_INIT_MKPATH |
+ GIT_REPOSITORY_INIT_NO_REINIT |
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
+ GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
+
+ /* Workdir: path to sub-repo working directory */
+ error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.workdir_path = workdir.ptr;
+
+ /**
+ * Repodir: path to the sub-repo. sub-repo goes in:
+ * <repo-dir>/modules/<name>/ with a gitlink in the
+ * sub-repo workdir directory to that repository.
+ */
+ error = git_buf_join3(
+ &repodir, '/', git_repository_path(parent_repo), "modules", path);
+ if (error < 0)
+ goto cleanup;
+
+ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+
+cleanup:
+ git_buf_free(&workdir);
+ git_buf_free(&repodir);
+
+ *out = subrepo;
+
+ return error;
+}
+
+/**
+ * Callback to override sub-repository creation when
+ * cloning a sub-repository.
+ */
+static int git_submodule_update_repo_init_cb(
+ git_repository **out,
+ const char *path,
+ int bare,
+ void *payload)
+{
+ git_submodule *sm;
+
+ GIT_UNUSED(bare);
+
+ sm = payload;
+
+ return submodule_repo_create(out, sm->repo, path);
+}
+
+int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT);
+ return 0;
+}
+
+int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options)
+{
+ int error;
+ unsigned int submodule_status;
+ git_config *config = NULL;
+ const char *submodule_url;
+ git_repository *sub_repo = NULL;
+ git_remote *remote = NULL;
+ git_object *target_commit = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
+ git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT;
+
+ assert(sm);
+
+ if (_update_options)
+ memcpy(&update_options, _update_options, sizeof(git_submodule_update_options));
+
+ GITERR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
+
+ /* Copy over the remote callbacks */
+ memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options));
+
+ /* Get the status of the submodule to determine if it is already initialized */
+ if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0)
+ goto done;
+
+ /*
+ * If submodule work dir is not already initialized, check to see
+ * what we need to do (initialize, clone, return error...)
+ */
+ if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) {
+ /*
+ * Work dir is not initialized, check to see if the submodule
+ * info has been copied into .git/config
+ */
+ if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
+ (error = git_buf_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0)
+ goto done;
+
+ if ((error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0) {
+ /*
+ * If the error is not "not found" or if it is "not found" and we are not
+ * initializing the submodule, then return error.
+ */
+ if (error != GIT_ENOTFOUND)
+ goto done;
+
+ if (!init) {
+ giterr_set(GITERR_SUBMODULE, "Submodule is not initialized.");
+ error = GIT_ERROR;
+ goto done;
+ }
+
+ /* The submodule has not been initialized yet - initialize it now.*/
+ if ((error = git_submodule_init(sm, 0)) < 0)
+ goto done;
+
+ git_config_free(config);
+ config = NULL;
+
+ if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
+ (error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0)
+ goto done;
+ }
+
+ /** submodule is initialized - now clone it **/
+ /* override repo creation */
+ clone_options.repository_cb = git_submodule_update_repo_init_cb;
+ clone_options.repository_cb_payload = sm;
+
+ /*
+ * Do not perform checkout as part of clone, instead we
+ * will checkout the specific commit manually.
+ */
+ clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;
+ update_options.checkout_opts.checkout_strategy = update_options.clone_checkout_strategy;
+
+ if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 ||
+ (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 ||
+ (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0)
+ goto done;
+ } else {
+ /**
+ * Work dir is initialized - look up the commit in the parent repository's index,
+ * update the workdir contents of the subrepository, and set the subrepository's
+ * head to the new commit.
+ */
+ if ((error = git_submodule_open(&sub_repo, sm)) < 0)
+ goto done;
+
+ /* Look up the target commit in the submodule. */
+ if ((error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0) {
+ /* If it isn't found then fetch and try again. */
+ if (error != GIT_ENOTFOUND || !update_options.allow_fetch ||
+ (error = lookup_default_remote(&remote, sub_repo)) < 0 ||
+ (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 ||
+ (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0)
+ goto done;
+ }
+
+ if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
+ (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0)
+ goto done;
+
+ /* Invalidate the wd flags as the workdir has been updated. */
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_SCANNED);
+ }
+
+done:
+ git_buf_free(&buf);
+ git_config_free(config);
+ git_object_free(target_commit);
+ git_remote_free(remote);
+ git_repository_free(sub_repo);
+
+ return error;
+}
+
+int git_submodule_init(git_submodule *sm, int overwrite)
+{
+ int error;
+ const char *val;
+ git_buf key = GIT_BUF_INIT, effective_submodule_url = GIT_BUF_INIT;
+ git_config *cfg = NULL;
+
+ if (!sm->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", sm->name);
+ return -1;
+ }
+
+ if ((error = git_repository_config(&cfg, sm->repo)) < 0)
+ return error;
+
+ /* write "submodule.NAME.url" */
+
+ if ((error = git_submodule_resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 ||
+ (error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
+ (error = git_config__update_entry(
+ cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0)
+ goto cleanup;
+
+ /* write "submodule.NAME.update" if not default */
+
+ val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ NULL : git_submodule_update_to_str(sm->update);
+
+ if ((error = git_buf_printf(&key, "submodule.%s.update", sm->name)) < 0 ||
+ (error = git_config__update_entry(
+ cfg, key.ptr, val, overwrite != 0, false)) < 0)
+ goto cleanup;
+
+ /* success */
+
+cleanup:
+ git_config_free(cfg);
+ git_buf_free(&key);
+ git_buf_free(&effective_submodule_url);
+
+ return error;
+}
+
+int git_submodule_sync(git_submodule *sm)
+{
+ int error = 0;
+ git_config *cfg = NULL;
+ git_buf key = GIT_BUF_INIT;
+ git_repository *smrepo = NULL;
+
+ if (!sm->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", sm->name);
+ return -1;
+ }
+
+ /* copy URL over to config only if it already exists */
+
+ if (!(error = git_repository_config__weakptr(&cfg, sm->repo)) &&
+ !(error = git_buf_printf(&key, "submodule.%s.url", sm->name)))
+ error = git_config__update_entry(cfg, key.ptr, sm->url, true, true);
+
+ /* if submodule exists in the working directory, update remote url */
+
+ if (!error &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
+ !(error = git_submodule_open(&smrepo, sm)))
+ {
+ git_buf remote_name = GIT_BUF_INIT;
+
+ if ((error = git_repository_config__weakptr(&cfg, smrepo)) < 0)
+ /* return error from reading submodule config */;
+ else if ((error = lookup_head_remote_key(&remote_name, smrepo)) < 0) {
+ giterr_clear();
+ error = git_buf_sets(&key, "remote.origin.url");
+ } else {
+ error = git_buf_join3(
+ &key, '.', "remote", remote_name.ptr, "url");
+ git_buf_free(&remote_name);
+ }
+
+ if (!error)
+ error = git_config__update_entry(cfg, key.ptr, sm->url, true, false);
+
+ git_repository_free(smrepo);
+ }
+
+ git_buf_free(&key);
+
+ return error;
+}
+
+static int git_submodule__open(
+ git_repository **subrepo, git_submodule *sm, bool bare)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH;
+ const char *wd;
+
+ assert(sm && subrepo);
+
+ if (git_repository__ensure_not_bare(
+ sm->repo, "open submodule repository") < 0)
+ return GIT_EBAREREPO;
+
+ wd = git_repository_workdir(sm->repo);
+
+ if (git_buf_joinpath(&path, wd, sm->path) < 0 ||
+ git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0)
+ return -1;
+
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_SCANNED);
+
+ if (bare)
+ flags |= GIT_REPOSITORY_OPEN_BARE;
+
+ error = git_repository_open_ext(subrepo, path.ptr, flags, wd);
+
+ /* if we opened the submodule successfully, grab HEAD OID, etc. */
+ if (!error) {
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
+ else
+ giterr_clear();
+ } else if (git_path_exists(path.ptr)) {
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED |
+ GIT_SUBMODULE_STATUS_IN_WD;
+ } else {
+ git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+ }
+
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, true);
+}
+
+int git_submodule_open(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, false);
+}
+
+static void submodule_update_from_index_entry(
+ git_submodule *sm, const git_index_entry *ie)
+{
+ bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0;
+
+ if (!S_ISGITLINK(ie->mode)) {
+ if (!already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else {
+ if (already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+ else
+ git_oid_cpy(&sm->index_oid, &ie->id);
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
+ }
+}
+
+static int submodule_update_index(git_submodule *sm)
+{
+ git_index *index;
+ const git_index_entry *ie;
+
+ if (git_repository_index__weakptr(&index, sm->repo) < 0)
+ return -1;
+
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
+
+ if (!(ie = git_index_get_bypath(index, sm->path, 0)))
+ return 0;
+
+ submodule_update_from_index_entry(sm, ie);
+
+ return 0;
+}
+
+static void submodule_update_from_head_data(
+ git_submodule *sm, mode_t mode, const git_oid *id)
+{
+ if (!S_ISGITLINK(mode))
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ else {
+ git_oid_cpy(&sm->head_oid, id);
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+ }
+}
+
+static int submodule_update_head(git_submodule *submodule)
+{
+ git_tree *head = NULL;
+ git_tree_entry *te = NULL;
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
+
+ /* if we can't look up file in current head, then done */
+ if (git_repository_head_tree(&head, submodule->repo) < 0 ||
+ git_tree_entry_bypath(&te, head, submodule->path) < 0)
+ giterr_clear();
+ else
+ submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te));
+
+ git_tree_entry_free(te);
+ git_tree_free(head);
+ return 0;
+}
+
+int git_submodule_reload(git_submodule *sm, int force)
+{
+ int error = 0;
+ git_config *mods;
+
+ GIT_UNUSED(force);
+
+ assert(sm);
+
+ if (!git_repository_is_bare(sm->repo)) {
+ /* refresh config data */
+ mods = gitmodules_snapshot(sm->repo);
+ if (mods != NULL) {
+ error = submodule_read_config(sm, mods);
+ git_config_free(mods);
+
+ if (error < 0)
+ return error;
+ }
+
+ /* refresh wd data */
+ sm->flags &=
+ ~(GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_FLAGS);
+
+ error = submodule_load_from_wd_lite(sm);
+ }
+
+ if (error == 0 && (error = submodule_update_index(sm)) == 0)
+ error = submodule_update_head(sm);
+
+ return error;
+}
+
+static void submodule_copy_oid_maybe(
+ git_oid *tgt, const git_oid *src, bool valid)
+{
+ if (tgt) {
+ if (valid)
+ memcpy(tgt, src, sizeof(*tgt));
+ else
+ memset(tgt, 0, sizeof(*tgt));
+ }
+}
+
+int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign)
+{
+ unsigned int status;
+ git_repository *smrepo = NULL;
+
+ if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED)
+ ign = sm->ignore;
+
+ /* only return location info if ignore == all */
+ if (ign == GIT_SUBMODULE_IGNORE_ALL) {
+ *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS);
+ return 0;
+ }
+
+ /* refresh the index OID */
+ if (submodule_update_index(sm) < 0)
+ return -1;
+
+ /* refresh the HEAD OID */
+ if (submodule_update_head(sm) < 0)
+ return -1;
+
+ /* for ignore == dirty, don't scan the working directory */
+ if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
+ /* git_submodule_open_bare will load WD OID data */
+ if (git_submodule_open_bare(&smrepo, sm) < 0)
+ giterr_clear();
+ else
+ git_repository_free(smrepo);
+ smrepo = NULL;
+ } else if (git_submodule_open(&smrepo, sm) < 0) {
+ giterr_clear();
+ smrepo = NULL;
+ }
+
+ status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags);
+
+ submodule_get_index_status(&status, sm);
+ submodule_get_wd_status(&status, sm, smrepo, ign);
+
+ git_repository_free(smrepo);
+
+ *out_status = status;
+
+ submodule_copy_oid_maybe(out_head_id, &sm->head_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_index_id, &sm->index_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0);
+
+ return 0;
+}
+
+int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore)
+{
+ git_submodule *sm;
+ int error;
+
+ assert(status && repo && name);
+
+ if ((error = git_submodule_lookup(&sm, repo, name)) < 0)
+ return error;
+
+ error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore);
+ git_submodule_free(sm);
+
+ return error;
+}
+
+int git_submodule_location(unsigned int *location, git_submodule *sm)
+{
+ assert(location && sm);
+
+ return git_submodule__status(
+ location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
+}
+
+
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+static int submodule_alloc(
+ git_submodule **out, git_repository *repo, const char *name)
+{
+ size_t namelen;
+ git_submodule *sm;
+
+ if (!name || !(namelen = strlen(name))) {
+ giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
+ return -1;
+ }
+
+ sm = git__calloc(1, sizeof(git_submodule));
+ GITERR_CHECK_ALLOC(sm);
+
+ sm->name = sm->path = git__strdup(name);
+ if (!sm->name) {
+ git__free(sm);
+ return -1;
+ }
+
+ GIT_REFCOUNT_INC(sm);
+ sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE;
+ sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO;
+ sm->repo = repo;
+ sm->branch = NULL;
+
+ *out = sm;
+ return 0;
+}
+
+static void submodule_release(git_submodule *sm)
+{
+ if (!sm)
+ return;
+
+ if (sm->repo) {
+ sm->repo = NULL;
+ }
+
+ if (sm->path != sm->name)
+ git__free(sm->path);
+ git__free(sm->name);
+ git__free(sm->url);
+ git__free(sm->branch);
+ git__memzero(sm, sizeof(*sm));
+ git__free(sm);
+}
+
+void git_submodule_free(git_submodule *sm)
+{
+ if (!sm)
+ return;
+ GIT_REFCOUNT_DEC(sm, submodule_release);
+}
+
+static int submodule_config_error(const char *property, const char *value)
+{
+ giterr_set(GITERR_INVALID,
+ "Invalid value for submodule '%s' property: '%s'", property, value);
+ return -1;
+}
+
+int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) {
+ *out = GIT_SUBMODULE_IGNORE_NONE;
+ return submodule_config_error("ignore", value);
+ }
+
+ *out = (git_submodule_ignore_t)val;
+ return 0;
+}
+
+int git_submodule_parse_update(git_submodule_update_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) {
+ *out = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ return submodule_config_error("update", value);
+ }
+
+ *out = (git_submodule_update_t)val;
+ return 0;
+}
+
+int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) {
+ *out = GIT_SUBMODULE_RECURSE_YES;
+ return submodule_config_error("recurse", value);
+ }
+
+ *out = (git_submodule_recurse_t)val;
+ return 0;
+}
+
+static int get_value(const char **out, git_config *cfg, git_buf *buf, const char *name, const char *field)
+{
+ int error;
+
+ git_buf_clear(buf);
+
+ if ((error = git_buf_printf(buf, "submodule.%s.%s", name, field)) < 0 ||
+ (error = git_config_get_string(out, cfg, buf->ptr)) < 0)
+ return error;
+
+ return error;
+}
+
+static int submodule_read_config(git_submodule *sm, git_config *cfg)
+{
+ git_buf key = GIT_BUF_INIT;
+ const char *value;
+ int error, in_config = 0;
+
+ /*
+ * TODO: Look up path in index and if it is present but not a GITLINK
+ * then this should be deleted (at least to match git's behavior)
+ */
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) {
+ in_config = 1;
+ /*
+ * TODO: if case insensitive filesystem, then the following strcmp
+ * should be strcasecmp
+ */
+ if (strcmp(sm->name, value) != 0) {
+ if (sm->path != sm->name)
+ git__free(sm->path);
+ sm->path = git__strdup(value);
+ GITERR_CHECK_ALLOC(sm->path);
+ }
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) {
+ in_config = 1;
+ sm->url = git__strdup(value);
+ GITERR_CHECK_ALLOC(sm->url);
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) {
+ in_config = 1;
+ sm->branch = git__strdup(value);
+ GITERR_CHECK_ALLOC(sm->branch);
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) {
+ in_config = 1;
+ if ((error = git_submodule_parse_update(&sm->update, value)) < 0)
+ goto cleanup;
+ sm->update_default = sm->update;
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) {
+ in_config = 1;
+ if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0)
+ goto cleanup;
+ sm->fetch_recurse_default = sm->fetch_recurse;
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) {
+ in_config = 1;
+ if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0)
+ goto cleanup;
+ sm->ignore_default = sm->ignore;
+ } else if (error != GIT_ENOTFOUND) {
+ goto cleanup;
+ }
+
+ if (in_config)
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&key);
+ return error;
+}
+
+static int submodule_load_each(const git_config_entry *entry, void *payload)
+{
+ lfc_data *data = payload;
+ const char *namestart, *property;
+ git_strmap_iter pos;
+ git_strmap *map = data->map;
+ git_buf name = GIT_BUF_INIT;
+ git_submodule *sm;
+ int error;
+
+ if (git__prefixcmp(entry->name, "submodule.") != 0)
+ return 0;
+
+ namestart = entry->name + strlen("submodule.");
+ property = strrchr(namestart, '.');
+
+ if (!property || (property == namestart))
+ return 0;
+
+ property++;
+
+ if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0)
+ return error;
+
+ /*
+ * Now that we have the submodule's name, we can use that to
+ * figure out whether it's in the map. If it's not, we create
+ * a new submodule, load the config and insert it. If it's
+ * already inserted, we've already loaded it, so we skip.
+ */
+ pos = git_strmap_lookup_index(map, name.ptr);
+ if (git_strmap_valid_index(map, pos)) {
+ error = 0;
+ goto done;
+ }
+
+ if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0)
+ goto done;
+
+ if ((error = submodule_read_config(sm, data->mods)) < 0) {
+ git_submodule_free(sm);
+ goto done;
+ }
+
+ git_strmap_insert(map, sm->name, sm, error);
+ assert(error != 0);
+ if (error < 0)
+ goto done;
+
+ error = 0;
+
+done:
+ git_buf_free(&name);
+ return error;
+}
+
+static int submodule_load_from_wd_lite(git_submodule *sm)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0)
+ return -1;
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (git_path_contains(&path, DOT_GIT))
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
+
+ git_buf_free(&path);
+ return 0;
+}
+
+/**
+ * Returns a snapshot of $WORK_TREE/.gitmodules.
+ *
+ * We ignore any errors and just pretend the file isn't there.
+ */
+static git_config *gitmodules_snapshot(git_repository *repo)
+{
+ const char *workdir = git_repository_workdir(repo);
+ git_config *mods = NULL, *snap = NULL;
+ git_buf path = GIT_BUF_INIT;
+
+ if (workdir != NULL) {
+ if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
+ return NULL;
+
+ if (git_config_open_ondisk(&mods, path.ptr) < 0)
+ mods = NULL;
+ }
+
+ git_buf_free(&path);
+
+ if (mods) {
+ git_config_snapshot(&snap, mods);
+ git_config_free(mods);
+ }
+
+ return snap;
+}
+
+static git_config_backend *open_gitmodules(
+ git_repository *repo,
+ int okay_to_create)
+{
+ const char *workdir = git_repository_workdir(repo);
+ git_buf path = GIT_BUF_INIT;
+ git_config_backend *mods = NULL;
+
+ if (workdir != NULL) {
+ if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
+ return NULL;
+
+ if (okay_to_create || git_path_isfile(path.ptr)) {
+ /* git_config_file__ondisk should only fail if OOM */
+ if (git_config_file__ondisk(&mods, path.ptr) < 0)
+ mods = NULL;
+ /* open should only fail here if the file is malformed */
+ else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ git_config_file_free(mods);
+ mods = NULL;
+ }
+ }
+ }
+
+ git_buf_free(&path);
+
+ return mods;
+}
+
+/* Lookup name of remote of the local tracking branch HEAD points to */
+static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo)
+{
+ int error;
+ git_reference *head = NULL;
+ git_buf upstream_name = GIT_BUF_INIT;
+
+ /* lookup and dereference HEAD */
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ /**
+ * If head does not refer to a branch, then return
+ * GIT_ENOTFOUND to indicate that we could not find
+ * a remote key for the local tracking branch HEAD points to.
+ **/
+ if (!git_reference_is_branch(head)) {
+ giterr_set(GITERR_INVALID,
+ "HEAD does not refer to a branch.");
+ error = GIT_ENOTFOUND;
+ goto done;
+ }
+
+ /* lookup remote tracking branch of HEAD */
+ if ((error = git_branch_upstream_name(
+ &upstream_name,
+ repo,
+ git_reference_name(head))) < 0)
+ goto done;
+
+ /* lookup remote of remote tracking branch */
+ if ((error = git_branch_remote_name(remote_name, repo, upstream_name.ptr)) < 0)
+ goto done;
+
+done:
+ git_buf_free(&upstream_name);
+ git_reference_free(head);
+
+ return error;
+}
+
+/* Lookup the remote of the local tracking branch HEAD points to */
+static int lookup_head_remote(git_remote **remote, git_repository *repo)
+{
+ int error;
+ git_buf remote_name = GIT_BUF_INIT;
+
+ /* lookup remote of remote tracking branch name */
+ if (!(error = lookup_head_remote_key(&remote_name, repo)))
+ error = git_remote_lookup(remote, repo, remote_name.ptr);
+
+ git_buf_free(&remote_name);
+
+ return error;
+}
+
+/* Lookup remote, either from HEAD or fall back on origin */
+static int lookup_default_remote(git_remote **remote, git_repository *repo)
+{
+ int error = lookup_head_remote(remote, repo);
+
+ /* if that failed, use 'origin' instead */
+ if (error == GIT_ENOTFOUND)
+ error = git_remote_lookup(remote, repo, "origin");
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(
+ GITERR_SUBMODULE,
+ "Cannot get default remote for submodule - no local tracking "
+ "branch for HEAD and origin does not exist");
+
+ return error;
+}
+
+static int get_url_base(git_buf *url, git_repository *repo)
+{
+ int error;
+ git_remote *remote = NULL;
+
+ if (!(error = lookup_default_remote(&remote, repo))) {
+ error = git_buf_sets(url, git_remote_url(remote));
+ git_remote_free(remote);
+ }
+ else if (error == GIT_ENOTFOUND) {
+ /* if repository does not have a default remote, use workdir instead */
+ giterr_clear();
+ error = git_buf_sets(url, git_repository_workdir(repo));
+ }
+
+ return error;
+}
+
+static void submodule_get_index_status(unsigned int *status, git_submodule *sm)
+{
+ const git_oid *head_oid = git_submodule_head_id(sm);
+ const git_oid *index_oid = git_submodule_index_id(sm);
+
+ *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS;
+
+ if (!head_oid) {
+ if (index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
+ }
+ else if (!index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
+ else if (!git_oid_equal(head_oid, index_oid))
+ *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
+}
+
+
+static void submodule_get_wd_status(
+ unsigned int *status,
+ git_submodule *sm,
+ git_repository *sm_repo,
+ git_submodule_ignore_t ign)
+{
+ const git_oid *index_oid = git_submodule_index_id(sm);
+ const git_oid *wd_oid =
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL;
+ git_tree *sm_head = NULL;
+ git_index *index = NULL;
+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff;
+
+ *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS;
+
+ if (!index_oid) {
+ if (wd_oid)
+ *status |= GIT_SUBMODULE_STATUS_WD_ADDED;
+ }
+ else if (!wd_oid) {
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
+ else
+ *status |= GIT_SUBMODULE_STATUS_WD_DELETED;
+ }
+ else if (!git_oid_equal(index_oid, wd_oid))
+ *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
+
+ /* if we have no repo, then we're done */
+ if (!sm_repo)
+ return;
+
+ /* the diffs below could be optimized with an early termination
+ * option to the git_diff functions, but for now this is sufficient
+ * (and certainly no worse that what core git does).
+ */
+
+ if (ign == GIT_SUBMODULE_IGNORE_NONE)
+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ (void)git_repository_index__weakptr(&index, sm_repo);
+
+ /* if we don't have an unborn head, check diff with index */
+ if (git_repository_head_tree(&sm_head, sm_repo) < 0)
+ giterr_clear();
+ else {
+ /* perform head to index diff on submodule */
+ if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0)
+ giterr_clear();
+ else {
+ if (git_diff_num_deltas(diff) > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
+ git_diff_free(diff);
+ diff = NULL;
+ }
+
+ git_tree_free(sm_head);
+ }
+
+ /* perform index-to-workdir diff on submodule */
+ if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0)
+ giterr_clear();
+ else {
+ size_t untracked =
+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
+
+ if (untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+
+ if (git_diff_num_deltas(diff) != untracked)
+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+
+ git_diff_free(diff);
+ diff = NULL;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_submodule_h__
+#define INCLUDE_submodule_h__
+
+#include "git2/submodule.h"
+#include "git2/repository.h"
+#include "fileops.h"
+
+/* Notes:
+ *
+ * Submodule information can be in four places: the index, the config files
+ * (both .git/config and .gitmodules), the HEAD tree, and the working
+ * directory.
+ *
+ * In the index:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the HEAD tree:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the config files:
+ * - submodule is found by submodule "name" which is usually the path
+ * - may be missing or present
+ * - will have a name, path, url, and other properties
+ *
+ * In the working directory:
+ * - submodule is found by path
+ * - may be missing, an empty directory, a checked out directory,
+ * or of the wrong type
+ * - if checked out, will have a HEAD oid
+ * - if checked out, will have git history that can be used to compare oids
+ * - if checked out, may have modified files and/or untracked files
+ */
+
+/**
+ * Description of submodule
+ *
+ * This record describes a submodule found in a repository. There should be
+ * an entry for every submodule found in the HEAD and index, and for every
+ * submodule described in .gitmodules. The fields are as follows:
+ *
+ * - `rc` tracks the refcount of how many hash table entries in the
+ * git_submodule_cache there are for this submodule. It only comes into
+ * play if the name and path of the submodule differ.
+ *
+ * - `name` is the name of the submodule from .gitmodules.
+ * - `path` is the path to the submodule from the repo root. It is almost
+ * always the same as `name`.
+ * - `url` is the url for the submodule.
+ * - `update` is a git_submodule_update_t value - see gitmodules(5) update.
+ * - `update_default` is the update value from the config
+ * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.
+ * - `ignore_default` is the ignore value from the config
+ * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5)
+ * fetchRecurseSubmodules.
+ * - `fetch_recurse_default` is the recurse value from the config
+ *
+ * - `repo` is the parent repository that contains this submodule.
+ * - `flags` after for internal use, tracking where this submodule has been
+ * found (head, index, config, workdir) and known status info, etc.
+ * - `head_oid` is the SHA1 for the submodule path in the repo HEAD.
+ * - `index_oid` is the SHA1 for the submodule recorded in the index.
+ * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule.
+ *
+ * If the submodule has been added to .gitmodules but not yet git added,
+ * then the `index_oid` will be zero but still marked valid. If the
+ * submodule has been deleted, but the delete has not been committed yet,
+ * then the `index_oid` will be set, but the `url` will be NULL.
+ */
+struct git_submodule {
+ git_refcount rc;
+
+ /* information from config */
+ char *name;
+ char *path; /* important: may just point to "name" string */
+ char *url;
+ char *branch;
+ git_submodule_update_t update;
+ git_submodule_update_t update_default;
+ git_submodule_ignore_t ignore;
+ git_submodule_ignore_t ignore_default;
+ git_submodule_recurse_t fetch_recurse;
+ git_submodule_recurse_t fetch_recurse_default;
+
+ /* internal information */
+ git_repository *repo;
+ uint32_t flags;
+ git_oid head_oid;
+ git_oid index_oid;
+ git_oid wd_oid;
+};
+
+/* Force revalidation of submodule data cache (alloc as needed) */
+extern int git_submodule_cache_refresh(git_repository *repo);
+
+/* Release all submodules */
+extern void git_submodule_cache_free(git_repository *repo);
+
+/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
+enum {
+ GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),
+ GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),
+ GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),
+ GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
+};
+
+#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
+ ((S) & ~(0xFFFFFFFFu << 20))
+
+/* Internal lookup does not attempt to refresh cached data */
+extern int git_submodule__lookup(
+ git_submodule **out, git_repository *repo, const char *path);
+
+/* Internal status fn returns status and optionally the various OIDs */
+extern int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign);
+
+/* Open submodule repository as bare repo for quick HEAD check, etc. */
+extern int git_submodule_open_bare(
+ git_repository **repo,
+ git_submodule *submodule);
+
+extern int git_submodule_parse_ignore(
+ git_submodule_ignore_t *out, const char *value);
+extern int git_submodule_parse_update(
+ git_submodule_update_t *out, const char *value);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "sysdir.h"
+#include "global.h"
+#include "buffer.h"
+#include "path.h"
+#include <ctype.h>
+#if GIT_WIN32
+#include "win32/findfile.h"
+#endif
+
+static int git_sysdir_guess_programdata_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_programdata_dirs(out);
+#else
+ git_buf_clear(out);
+ return 0;
+#endif
+}
+
+static int git_sysdir_guess_system_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_system_dirs(out, L"etc\\");
+#else
+ return git_buf_sets(out, "/etc");
+#endif
+}
+
+static int git_sysdir_guess_global_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_global_dirs(out);
+#else
+ int error = git__getenv(out, "HOME");
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+#endif
+}
+
+static int git_sysdir_guess_xdg_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_xdg_dirs(out);
+#else
+ git_buf env = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0)
+ error = git_buf_joinpath(out, env.ptr, "git");
+
+ if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0)
+ error = git_buf_joinpath(out, env.ptr, ".config/git");
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ git_buf_free(&env);
+ return error;
+#endif
+}
+
+static int git_sysdir_guess_template_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_system_dirs(out, L"share\\git-core\\templates");
+#else
+ return git_buf_sets(out, "/usr/share/git-core/templates");
+#endif
+}
+
+struct git_sysdir__dir {
+ git_buf buf;
+ int (*guess)(git_buf *out);
+};
+
+static struct git_sysdir__dir git_sysdir__dirs[] = {
+ { GIT_BUF_INIT, git_sysdir_guess_system_dirs },
+ { GIT_BUF_INIT, git_sysdir_guess_global_dirs },
+ { GIT_BUF_INIT, git_sysdir_guess_xdg_dirs },
+ { GIT_BUF_INIT, git_sysdir_guess_programdata_dirs },
+ { GIT_BUF_INIT, git_sysdir_guess_template_dirs },
+};
+
+static void git_sysdir_global_shutdown(void)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(git_sysdir__dirs); ++i)
+ git_buf_free(&git_sysdir__dirs[i].buf);
+}
+
+int git_sysdir_global_init(void)
+{
+ size_t i;
+ int error = 0;
+
+ for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); i++)
+ error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf);
+
+ git__on_shutdown(git_sysdir_global_shutdown);
+
+ return error;
+}
+
+static int git_sysdir_check_selector(git_sysdir_t which)
+{
+ if (which < ARRAY_SIZE(git_sysdir__dirs))
+ return 0;
+
+ giterr_set(GITERR_INVALID, "config directory selector out of range");
+ return -1;
+}
+
+
+int git_sysdir_get(const git_buf **out, git_sysdir_t which)
+{
+ assert(out);
+
+ *out = NULL;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+
+ *out = &git_sysdir__dirs[which].buf;
+ return 0;
+}
+
+int git_sysdir_get_str(
+ char *out,
+ size_t outlen,
+ git_sysdir_t which)
+{
+ const git_buf *path = NULL;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+ GITERR_CHECK_ERROR(git_sysdir_get(&path, which));
+
+ if (!out || path->size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ return GIT_EBUFS;
+ }
+
+ git_buf_copy_cstr(out, outlen, path);
+ return 0;
+}
+
+#define PATH_MAGIC "$PATH"
+
+int git_sysdir_set(git_sysdir_t which, const char *search_path)
+{
+ const char *expand_path = NULL;
+ git_buf merge = GIT_BUF_INIT;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+
+ if (search_path != NULL)
+ expand_path = strstr(search_path, PATH_MAGIC);
+
+ /* reset the default if this path has been cleared */
+ if (!search_path)
+ git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf);
+
+ /* if $PATH is not referenced, then just set the path */
+ if (!expand_path) {
+ if (search_path)
+ git_buf_sets(&git_sysdir__dirs[which].buf, search_path);
+
+ goto done;
+ }
+
+ /* otherwise set to join(before $PATH, old value, after $PATH) */
+ if (expand_path > search_path)
+ git_buf_set(&merge, search_path, expand_path - search_path);
+
+ if (git_buf_len(&git_sysdir__dirs[which].buf))
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR,
+ merge.ptr, git_sysdir__dirs[which].buf.ptr);
+
+ expand_path += strlen(PATH_MAGIC);
+ if (*expand_path)
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path);
+
+ git_buf_swap(&git_sysdir__dirs[which].buf, &merge);
+ git_buf_free(&merge);
+
+done:
+ if (git_buf_oom(&git_sysdir__dirs[which].buf))
+ return -1;
+
+ return 0;
+}
+
+static int git_sysdir_find_in_dirlist(
+ git_buf *path,
+ const char *name,
+ git_sysdir_t which,
+ const char *label)
+{
+ size_t len;
+ const char *scan, *next = NULL;
+ const git_buf *syspath;
+
+ GITERR_CHECK_ERROR(git_sysdir_get(&syspath, which));
+ if (!syspath || !git_buf_len(syspath))
+ goto done;
+
+ for (scan = git_buf_cstr(syspath); scan; scan = next) {
+ /* find unescaped separator or end of string */
+ for (next = scan; *next; ++next) {
+ if (*next == GIT_PATH_LIST_SEPARATOR &&
+ (next <= scan || next[-1] != '\\'))
+ break;
+ }
+
+ len = (size_t)(next - scan);
+ next = (*next ? next + 1 : NULL);
+ if (!len)
+ continue;
+
+ GITERR_CHECK_ERROR(git_buf_set(path, scan, len));
+ if (name)
+ GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
+
+ if (git_path_exists(path->ptr))
+ return 0;
+ }
+
+done:
+ git_buf_free(path);
+ giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);
+ return GIT_ENOTFOUND;
+}
+
+int git_sysdir_find_system_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_SYSTEM, "system");
+}
+
+int git_sysdir_find_global_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_GLOBAL, "global");
+}
+
+int git_sysdir_find_xdg_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_XDG, "global/xdg");
+}
+
+int git_sysdir_find_programdata_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData");
+}
+
+int git_sysdir_find_template_dir(git_buf *path)
+{
+ return git_sysdir_find_in_dirlist(
+ path, NULL, GIT_SYSDIR_TEMPLATE, "template");
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sysdir_h__
+#define INCLUDE_sysdir_h__
+
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
+
+/**
+ * Find a "global" file (i.e. one in a user's home directory).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_global_file(git_buf *path, const char *filename);
+
+/**
+ * Find an "XDG" file (i.e. one in user's XDG config path).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_xdg_file(git_buf *path, const char *filename);
+
+/**
+ * Find a "system" file (i.e. one shared for all users of the system).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_system_file(git_buf *path, const char *filename);
+
+/**
+ * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%)
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the ProgramData directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_programdata_file(git_buf *path, const char *filename);
+
+/**
+ * Find template directory.
+ *
+ * @param path buffer to write the full path into
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_template_dir(git_buf *path);
+
+typedef enum {
+ GIT_SYSDIR_SYSTEM = 0,
+ GIT_SYSDIR_GLOBAL = 1,
+ GIT_SYSDIR_XDG = 2,
+ GIT_SYSDIR_PROGRAMDATA = 3,
+ GIT_SYSDIR_TEMPLATE = 4,
+ GIT_SYSDIR__MAX = 5,
+} git_sysdir_t;
+
+/**
+ * Configures global data for configuration file search paths.
+ *
+ * @return 0 on success, <0 on failure
+ */
+extern int git_sysdir_global_init(void);
+
+/**
+ * Get the search path for global/system/xdg files
+ *
+ * @param out pointer to git_buf containing search path
+ * @param which which list of paths to return
+ * @return 0 on success, <0 on failure
+ */
+extern int git_sysdir_get(const git_buf **out, git_sysdir_t which);
+
+/**
+ * Get search path into a preallocated buffer
+ *
+ * @param out String buffer to write into
+ * @param outlen Size of string buffer
+ * @param which Which search path to return
+ * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure
+ */
+
+extern int git_sysdir_get_str(char *out, size_t outlen, git_sysdir_t which);
+
+/**
+ * Set search paths for global/system/xdg files
+ *
+ * The first occurrence of the magic string "$PATH" in the new value will
+ * be replaced with the old value of the search path.
+ *
+ * @param which Which search path to modify
+ * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR)
+ * @return 0 on success, <0 on failure (allocation error)
+ */
+extern int git_sysdir_set(git_sysdir_t which, const char *paths);
+
+#endif /* INCLUDE_sysdir_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "signature.h"
+#include "message.h"
+#include "git2/object.h"
+#include "git2/repository.h"
+#include "git2/signature.h"
+#include "git2/odb_backend.h"
+
+void git_tag__free(void *_tag)
+{
+ git_tag *tag = _tag;
+ git_signature_free(tag->tagger);
+ git__free(tag->message);
+ git__free(tag->tag_name);
+ git__free(tag);
+}
+
+int git_tag_target(git_object **target, const git_tag *t)
+{
+ assert(t);
+ return git_object_lookup(target, t->object.repo, &t->target, t->type);
+}
+
+const git_oid *git_tag_target_id(const git_tag *t)
+{
+ assert(t);
+ return &t->target;
+}
+
+git_otype git_tag_target_type(const git_tag *t)
+{
+ assert(t);
+ return t->type;
+}
+
+const char *git_tag_name(const git_tag *t)
+{
+ assert(t);
+ return t->tag_name;
+}
+
+const git_signature *git_tag_tagger(const git_tag *t)
+{
+ return t->tagger;
+}
+
+const char *git_tag_message(const git_tag *t)
+{
+ assert(t);
+ return t->message;
+}
+
+static int tag_error(const char *str)
+{
+ giterr_set(GITERR_TAG, "Failed to parse tag. %s", str);
+ return -1;
+}
+
+static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end)
+{
+ static const char *tag_types[] = {
+ NULL, "commit\n", "tree\n", "blob\n", "tag\n"
+ };
+
+ unsigned int i;
+ size_t text_len, alloc_len;
+ char *search;
+
+ if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0)
+ return tag_error("Object field invalid");
+
+ if (buffer + 5 >= buffer_end)
+ return tag_error("Object too short");
+
+ if (memcmp(buffer, "type ", 5) != 0)
+ return tag_error("Type field not found");
+ buffer += 5;
+
+ tag->type = GIT_OBJ_BAD;
+
+ for (i = 1; i < ARRAY_SIZE(tag_types); ++i) {
+ size_t type_length = strlen(tag_types[i]);
+
+ if (buffer + type_length >= buffer_end)
+ return tag_error("Object too short");
+
+ if (memcmp(buffer, tag_types[i], type_length) == 0) {
+ tag->type = i;
+ buffer += type_length;
+ break;
+ }
+ }
+
+ if (tag->type == GIT_OBJ_BAD)
+ return tag_error("Invalid object type");
+
+ if (buffer + 4 >= buffer_end)
+ return tag_error("Object too short");
+
+ if (memcmp(buffer, "tag ", 4) != 0)
+ return tag_error("Tag field not found");
+
+ buffer += 4;
+
+ search = memchr(buffer, '\n', buffer_end - buffer);
+ if (search == NULL)
+ return tag_error("Object too short");
+
+ text_len = search - buffer;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1);
+ tag->tag_name = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(tag->tag_name);
+
+ memcpy(tag->tag_name, buffer, text_len);
+ tag->tag_name[text_len] = '\0';
+
+ buffer = search + 1;
+
+ tag->tagger = NULL;
+ if (buffer < buffer_end && *buffer != '\n') {
+ tag->tagger = git__malloc(sizeof(git_signature));
+ GITERR_CHECK_ALLOC(tag->tagger);
+
+ if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0)
+ return -1;
+ }
+
+ tag->message = NULL;
+ if (buffer < buffer_end) {
+ /* If we're not at the end of the header, search for it */
+ if( *buffer != '\n' ) {
+ search = strstr(buffer, "\n\n");
+ if (search)
+ buffer = search + 1;
+ else
+ return tag_error("tag contains no message");
+ }
+
+ text_len = buffer_end - ++buffer;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1);
+ tag->message = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(tag->message);
+
+ memcpy(tag->message, buffer, text_len);
+ tag->message[text_len] = '\0';
+ }
+
+ return 0;
+}
+
+int git_tag__parse(void *_tag, git_odb_object *odb_obj)
+{
+ git_tag *tag = _tag;
+ const char *buffer = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
+
+ return tag_parse(tag, buffer, buffer_end);
+}
+
+static int retrieve_tag_reference(
+ git_reference **tag_reference_out,
+ git_buf *ref_name_out,
+ git_repository *repo,
+ const char *tag_name)
+{
+ git_reference *tag_ref;
+ int error;
+
+ *tag_reference_out = NULL;
+
+ if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
+ return -1;
+
+ error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr);
+ if (error < 0)
+ return error; /* Be it not foundo or corrupted */
+
+ *tag_reference_out = tag_ref;
+
+ return 0;
+}
+
+static int retrieve_tag_reference_oid(
+ git_oid *oid,
+ git_buf *ref_name_out,
+ git_repository *repo,
+ const char *tag_name)
+{
+ if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
+ return -1;
+
+ return git_reference_name_to_id(oid, repo, ref_name_out->ptr);
+}
+
+static int write_tag_annotation(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message)
+{
+ git_buf tag = GIT_BUF_INIT;
+ git_odb *odb;
+
+ git_oid__writebuf(&tag, "object ", git_object_id(target));
+ git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target)));
+ git_buf_printf(&tag, "tag %s\n", tag_name);
+ git_signature__writebuf(&tag, "tagger ", tagger);
+ git_buf_putc(&tag, '\n');
+
+ if (git_buf_puts(&tag, message) < 0)
+ goto on_error;
+
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ goto on_error;
+
+ if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0)
+ goto on_error;
+
+ git_buf_free(&tag);
+ return 0;
+
+on_error:
+ git_buf_free(&tag);
+ giterr_set(GITERR_OBJECT, "Failed to create tag annotation.");
+ return -1;
+}
+
+static int git_tag_create__internal(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int allow_ref_overwrite,
+ int create_tag_annotation)
+{
+ git_reference *new_ref = NULL;
+ git_buf ref_name = GIT_BUF_INIT;
+
+ int error;
+
+ assert(repo && tag_name && target);
+ assert(!create_tag_annotation || (tagger && message));
+
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_INVALID, "The given target does not belong to this repository");
+ return -1;
+ }
+
+ error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ /** Ensure the tag name doesn't conflict with an already existing
+ * reference unless overwriting has explicitly been requested **/
+ if (error == 0 && !allow_ref_overwrite) {
+ git_buf_free(&ref_name);
+ giterr_set(GITERR_TAG, "Tag already exists");
+ return GIT_EEXISTS;
+ }
+
+ if (create_tag_annotation) {
+ if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0)
+ return -1;
+ } else
+ git_oid_cpy(oid, git_object_id(target));
+
+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL);
+
+cleanup:
+ git_reference_free(new_ref);
+ git_buf_free(&ref_name);
+ return error;
+}
+
+int git_tag_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int allow_ref_overwrite)
+{
+ return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1);
+}
+
+int git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message)
+{
+ assert(oid && repo && tag_name && target && tagger && message);
+
+ return write_tag_annotation(oid, repo, tag_name, target, tagger, message);
+}
+
+int git_tag_create_lightweight(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ int allow_ref_overwrite)
+{
+ return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0);
+}
+
+int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite)
+{
+ git_tag tag;
+ int error;
+ git_odb *odb;
+ git_odb_stream *stream;
+ git_odb_object *target_obj;
+
+ git_reference *new_ref = NULL;
+ git_buf ref_name = GIT_BUF_INIT;
+
+ assert(oid && buffer);
+
+ memset(&tag, 0, sizeof(tag));
+
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ return -1;
+
+ /* validate the buffer */
+ if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0)
+ return -1;
+
+ /* validate the target */
+ if (git_odb_read(&target_obj, odb, &tag.target) < 0)
+ goto on_error;
+
+ if (tag.type != target_obj->cached.type) {
+ giterr_set(GITERR_TAG, "The type for the given target is invalid");
+ goto on_error;
+ }
+
+ error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ /* We don't need these objects after this */
+ git_signature_free(tag.tagger);
+ git__free(tag.tag_name);
+ git__free(tag.message);
+ git_odb_object_free(target_obj);
+
+ /** Ensure the tag name doesn't conflict with an already existing
+ * reference unless overwriting has explicitly been requested **/
+ if (error == 0 && !allow_ref_overwrite) {
+ giterr_set(GITERR_TAG, "Tag already exists");
+ return GIT_EEXISTS;
+ }
+
+ /* write the buffer */
+ if ((error = git_odb_open_wstream(
+ &stream, odb, strlen(buffer), GIT_OBJ_TAG)) < 0)
+ return error;
+
+ if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer))))
+ error = git_odb_stream_finalize_write(oid, stream);
+
+ git_odb_stream_free(stream);
+
+ if (error < 0) {
+ git_buf_free(&ref_name);
+ return error;
+ }
+
+ error = git_reference_create(
+ &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL);
+
+ git_reference_free(new_ref);
+ git_buf_free(&ref_name);
+
+ return error;
+
+on_error:
+ git_signature_free(tag.tagger);
+ git__free(tag.tag_name);
+ git__free(tag.message);
+ git_odb_object_free(target_obj);
+ return -1;
+}
+
+int git_tag_delete(git_repository *repo, const char *tag_name)
+{
+ git_reference *tag_ref;
+ git_buf ref_name = GIT_BUF_INIT;
+ int error;
+
+ error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name);
+
+ git_buf_free(&ref_name);
+
+ if (error < 0)
+ return error;
+
+ error = git_reference_delete(tag_ref);
+
+ git_reference_free(tag_ref);
+
+ return error;
+}
+
+typedef struct {
+ git_repository *repo;
+ git_tag_foreach_cb cb;
+ void *cb_data;
+} tag_cb_data;
+
+static int tags_cb(const char *ref, void *data)
+{
+ int error;
+ git_oid oid;
+ tag_cb_data *d = (tag_cb_data *)data;
+
+ if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0)
+ return 0; /* no tag */
+
+ if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) {
+ if ((error = d->cb(ref, &oid, d->cb_data)) != 0)
+ giterr_set_after_callback_function(error, "git_tag_foreach");
+ }
+
+ return error;
+}
+
+int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)
+{
+ tag_cb_data data;
+
+ assert(repo && cb);
+
+ data.cb = cb;
+ data.cb_data = cb_data;
+ data.repo = repo;
+
+ return git_reference_foreach_name(repo, &tags_cb, &data);
+}
+
+typedef struct {
+ git_vector *taglist;
+ const char *pattern;
+} tag_filter_data;
+
+#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR)
+
+static int tag_list_cb(const char *tag_name, git_oid *oid, void *data)
+{
+ tag_filter_data *filter = (tag_filter_data *)data;
+ GIT_UNUSED(oid);
+
+ if (!*filter->pattern ||
+ p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0)
+ {
+ char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN);
+ GITERR_CHECK_ALLOC(matched);
+
+ return git_vector_insert(filter->taglist, matched);
+ }
+
+ return 0;
+}
+
+int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo)
+{
+ int error;
+ tag_filter_data filter;
+ git_vector taglist;
+
+ assert(tag_names && repo && pattern);
+
+ if ((error = git_vector_init(&taglist, 8, NULL)) < 0)
+ return error;
+
+ filter.taglist = &taglist;
+ filter.pattern = pattern;
+
+ error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter);
+
+ if (error < 0)
+ git_vector_free(&taglist);
+
+ tag_names->strings =
+ (char **)git_vector_detach(&tag_names->count, NULL, &taglist);
+
+ return 0;
+}
+
+int git_tag_list(git_strarray *tag_names, git_repository *repo)
+{
+ return git_tag_list_match(tag_names, "", repo);
+}
+
+int git_tag_peel(git_object **tag_target, const git_tag *tag)
+{
+ return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_tag_h__
+#define INCLUDE_tag_h__
+
+#include "git2/tag.h"
+#include "repository.h"
+#include "odb.h"
+
+struct git_tag {
+ git_object object;
+
+ git_oid target;
+ git_otype type;
+
+ char *tag_name;
+ git_signature *tagger;
+ char *message;
+};
+
+void git_tag__free(void *tag);
+int git_tag__parse(void *tag, git_odb_object *obj);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "thread-utils.h"
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+# include <windows.h>
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+# include <sys/pstat.h>
+#endif
+
+/*
+ * By doing this in two steps we can at least get
+ * the function to be somewhat coherent, even
+ * with this disgusting nest of #ifdefs.
+ */
+#ifndef _SC_NPROCESSORS_ONLN
+# ifdef _SC_NPROC_ONLN
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# elif defined _SC_CRAY_NCPU
+# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+# endif
+#endif
+
+int git_online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ long ncpus;
+#endif
+
+#ifdef _WIN32
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+
+ if ((int)info.dwNumberOfProcessors > 0)
+ return (int)info.dwNumberOfProcessors;
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+ struct pst_dynamic psd;
+
+ if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0))
+ return (int)psd.psd_proc_cnt;
+#endif
+
+#ifdef _SC_NPROCESSORS_ONLN
+ if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0)
+ return (int)ncpus;
+#endif
+
+ return 1;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_thread_utils_h__
+#define INCLUDE_thread_utils_h__
+
+/* Common operations even if threading has been disabled */
+typedef struct {
+#if defined(GIT_WIN32)
+ volatile long val;
+#else
+ volatile int val;
+#endif
+} git_atomic;
+
+#ifdef GIT_ARCH_64
+
+typedef struct {
+#if defined(GIT_WIN32)
+ __int64 val;
+#else
+ int64_t val;
+#endif
+} git_atomic64;
+
+typedef git_atomic64 git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic64_add
+
+#else
+
+typedef git_atomic git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic_add
+
+#endif
+
+#ifdef GIT_THREADS
+
+#ifdef GIT_WIN32
+# include "win32/thread.h"
+#else
+# include "unix/pthread.h"
+#endif
+
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+#if defined(GIT_WIN32)
+ InterlockedExchange(&a->val, (LONG)val);
+#elif defined(__GNUC__)
+ __sync_lock_test_and_set(&a->val, val);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(int) git_atomic_inc(git_atomic *a)
+{
+#if defined(GIT_WIN32)
+ return InterlockedIncrement(&a->val);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(int) git_atomic_dec(git_atomic *a)
+{
+#if defined(GIT_WIN32)
+ return InterlockedDecrement(&a->val);
+#elif defined(__GNUC__)
+ return __sync_sub_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ volatile void *foundval;
+#if defined(GIT_WIN32)
+ foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
+#elif defined(__GNUC__)
+ foundval = __sync_val_compare_and_swap(ptr, oldval, newval);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+ return (foundval == oldval) ? oldval : newval;
+}
+
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangePointer(ptr, newval);
+#else
+ return __sync_lock_test_and_set(ptr, newval);
+#endif
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd64(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+#endif
+
+#else
+
+#define git_thread unsigned int
+#define git_thread_create(thread, start_routine, arg) 0
+#define git_thread_join(id, status) (void)0
+
+/* Pthreads Mutex */
+#define git_mutex unsigned int
+GIT_INLINE(int) git_mutex_init(git_mutex *mutex) \
+ { GIT_UNUSED(mutex); return 0; }
+GIT_INLINE(int) git_mutex_lock(git_mutex *mutex) \
+ { GIT_UNUSED(mutex); return 0; }
+#define git_mutex_unlock(a) (void)0
+#define git_mutex_free(a) (void)0
+
+/* Pthreads condition vars */
+#define git_cond unsigned int
+#define git_cond_init(c, a) (void)0
+#define git_cond_free(c) (void)0
+#define git_cond_wait(c, l) (void)0
+#define git_cond_signal(c) (void)0
+#define git_cond_broadcast(c) (void)0
+
+/* Pthreads rwlock */
+#define git_rwlock unsigned int
+#define git_rwlock_init(a) 0
+#define git_rwlock_rdlock(a) 0
+#define git_rwlock_rdunlock(a) (void)0
+#define git_rwlock_wrlock(a) 0
+#define git_rwlock_wrunlock(a) (void)0
+#define git_rwlock_free(a) (void)0
+#define GIT_RWLOCK_STATIC_INIT 0
+
+
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+ a->val = val;
+}
+
+GIT_INLINE(int) git_atomic_inc(git_atomic *a)
+{
+ return ++a->val;
+}
+
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
+GIT_INLINE(int) git_atomic_dec(git_atomic *a)
+{
+ return --a->val;
+}
+
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ if (*ptr == oldval)
+ *ptr = newval;
+ else
+ oldval = newval;
+ return oldval;
+}
+
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+ volatile void *old = *ptr;
+ *ptr = newval;
+ return old;
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
+#endif
+
+#endif
+
+GIT_INLINE(int) git_atomic_get(git_atomic *a)
+{
+ return (int)a->val;
+}
+
+/* Atomically replace oldval with newval
+ * @return oldval if it was replaced or newval if it was not
+ */
+#define git__compare_and_swap(P,O,N) \
+ git___compare_and_swap((void * volatile *)P, O, N)
+
+#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val)
+
+extern int git_online_cpus(void);
+
+#if defined(GIT_THREADS) && defined(_MSC_VER)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+#elif defined(GIT_THREADS)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+#else
+# define GIT_MEMORY_BARRIER /* noop */
+#endif
+
+#endif /* INCLUDE_thread_utils_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/errors.h"
+#include "common.h"
+
+#include "openssl_stream.h"
+#include "stransport_stream.h"
+
+static git_stream_cb tls_ctor;
+
+int git_stream_register_tls(git_stream_cb ctor)
+{
+ tls_ctor = ctor;
+
+ return 0;
+}
+
+int git_tls_stream_new(git_stream **out, const char *host, const char *port)
+{
+
+ if (tls_ctor)
+ return tls_ctor(out, host, port);
+
+#ifdef GIT_SECURE_TRANSPORT
+ return git_stransport_stream_new(out, host, port);
+#elif defined(GIT_OPENSSL)
+ return git_openssl_stream_new(out, host, port);
+#else
+ GIT_UNUSED(out);
+ GIT_UNUSED(host);
+ GIT_UNUSED(port);
+
+ giterr_set(GITERR_SSL, "there is no TLS stream available");
+ return -1;
+#endif
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_tls_stream_h__
+#define INCLUDE_tls_stream_h__
+
+#include "git2/sys/stream.h"
+
+/**
+ * Create a TLS stream with the most appropriate backend available for
+ * the current platform.
+ *
+ * This allows us to ask for a SecureTransport or OpenSSL stream
+ * according to being on general Unix vs OS X.
+ */
+extern int git_tls_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "buffer.h"
+#include "common.h"
+#include "global.h"
+#include "trace.h"
+#include "git2/trace.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data git_trace__data = {0};
+
+#endif
+
+int git_trace_set(git_trace_level_t level, git_trace_callback callback)
+{
+#ifdef GIT_TRACE
+ assert(level == 0 || callback != NULL);
+
+ git_trace__data.level = level;
+ git_trace__data.callback = callback;
+ GIT_MEMORY_BARRIER;
+
+ return 0;
+#else
+ GIT_UNUSED(level);
+ GIT_UNUSED(callback);
+
+ giterr_set(GITERR_INVALID,
+ "This version of libgit2 was not built with tracing.");
+ return -1;
+#endif
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_trace_h__
+#define INCLUDE_trace_h__
+
+#include <git2/trace.h>
+#include "buffer.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data {
+ git_trace_level_t level;
+ git_trace_callback callback;
+};
+
+extern struct git_trace_data git_trace__data;
+
+GIT_INLINE(void) git_trace__write_fmt(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ git_trace_callback callback = git_trace__data.callback;
+ git_buf message = GIT_BUF_INIT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ git_buf_vprintf(&message, fmt, ap);
+ va_end(ap);
+
+ callback(level, git_buf_cstr(&message));
+
+ git_buf_free(&message);
+}
+
+#define git_trace_level() (git_trace__data.level)
+#define git_trace(l, ...) { \
+ if (git_trace__data.level >= l && \
+ git_trace__data.callback != NULL) { \
+ git_trace__write_fmt(l, __VA_ARGS__); \
+ } \
+ }
+
+#else
+
+GIT_INLINE(void) git_trace__null(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ GIT_UNUSED(level);
+ GIT_UNUSED(fmt);
+}
+
+#define git_trace_level() ((void)0)
+#define git_trace git_trace__null
+
+#endif
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "strmap.h"
+#include "refdb.h"
+#include "pool.h"
+#include "reflog.h"
+#include "signature.h"
+#include "config.h"
+
+#include "git2/transaction.h"
+#include "git2/signature.h"
+#include "git2/sys/refs.h"
+#include "git2/sys/refdb_backend.h"
+
+GIT__USE_STRMAP
+
+typedef enum {
+ TRANSACTION_NONE,
+ TRANSACTION_REFS,
+ TRANSACTION_CONFIG,
+} transaction_t;
+
+typedef struct {
+ const char *name;
+ void *payload;
+
+ git_ref_t ref_type;
+ union {
+ git_oid id;
+ char *symbolic;
+ } target;
+ git_reflog *reflog;
+
+ const char *message;
+ git_signature *sig;
+
+ unsigned int committed :1,
+ remove :1;
+} transaction_node;
+
+struct git_transaction {
+ transaction_t type;
+ git_repository *repo;
+ git_refdb *db;
+ git_config *cfg;
+
+ git_strmap *locks;
+ git_pool pool;
+};
+
+int git_transaction_config_new(git_transaction **out, git_config *cfg)
+{
+ git_transaction *tx;
+ assert(out && cfg);
+
+ tx = git__calloc(1, sizeof(git_transaction));
+ GITERR_CHECK_ALLOC(tx);
+
+ tx->type = TRANSACTION_CONFIG;
+ tx->cfg = cfg;
+ *out = tx;
+ return 0;
+}
+
+int git_transaction_new(git_transaction **out, git_repository *repo)
+{
+ int error;
+ git_pool pool;
+ git_transaction *tx = NULL;
+
+ assert(out && repo);
+
+ git_pool_init(&pool, 1);
+
+ tx = git_pool_mallocz(&pool, sizeof(git_transaction));
+ if (!tx) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_strmap_alloc(&tx->locks)) < 0) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_repository_refdb(&tx->db, repo)) < 0)
+ goto on_error;
+
+ tx->type = TRANSACTION_REFS;
+ memcpy(&tx->pool, &pool, sizeof(git_pool));
+ tx->repo = repo;
+ *out = tx;
+ return 0;
+
+on_error:
+ git_pool_clear(&pool);
+ return error;
+}
+
+int git_transaction_lock_ref(git_transaction *tx, const char *refname)
+{
+ int error;
+ transaction_node *node;
+
+ assert(tx && refname);
+
+ node = git_pool_mallocz(&tx->pool, sizeof(transaction_node));
+ GITERR_CHECK_ALLOC(node);
+
+ node->name = git_pool_strdup(&tx->pool, refname);
+ GITERR_CHECK_ALLOC(node->name);
+
+ if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0)
+ return error;
+
+ git_strmap_insert(tx->locks, node->name, node, error);
+ if (error < 0)
+ goto cleanup;
+
+ return 0;
+
+cleanup:
+ git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
+
+ return error;
+}
+
+static int find_locked(transaction_node **out, git_transaction *tx, const char *refname)
+{
+ git_strmap_iter pos;
+ transaction_node *node;
+
+ pos = git_strmap_lookup_index(tx->locks, refname);
+ if (!git_strmap_valid_index(tx->locks, pos)) {
+ giterr_set(GITERR_REFERENCE, "the specified reference is not locked");
+ return GIT_ENOTFOUND;
+ }
+
+ node = git_strmap_value_at(tx->locks, pos);
+
+ *out = node;
+ return 0;
+}
+
+static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg)
+{
+ if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0)
+ return -1;
+
+ if (!node->sig) {
+ git_signature *tmp;
+ int error;
+
+ if (git_reference__log_signature(&tmp, tx->repo) < 0)
+ return -1;
+
+ /* make sure the sig we use is in our pool */
+ error = git_signature__pdup(&node->sig, tmp, &tx->pool);
+ git_signature_free(tmp);
+ if (error < 0)
+ return error;
+ }
+
+ if (msg) {
+ node->message = git_pool_strdup(&tx->pool, msg);
+ GITERR_CHECK_ALLOC(node->message);
+ }
+
+ return 0;
+}
+
+int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg)
+{
+ int error;
+ transaction_node *node;
+
+ assert(tx && refname && target);
+
+ if ((error = find_locked(&node, tx, refname)) < 0)
+ return error;
+
+ if ((error = copy_common(node, tx, sig, msg)) < 0)
+ return error;
+
+ git_oid_cpy(&node->target.id, target);
+ node->ref_type = GIT_REF_OID;
+
+ return 0;
+}
+
+int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg)
+{
+ int error;
+ transaction_node *node;
+
+ assert(tx && refname && target);
+
+ if ((error = find_locked(&node, tx, refname)) < 0)
+ return error;
+
+ if ((error = copy_common(node, tx, sig, msg)) < 0)
+ return error;
+
+ node->target.symbolic = git_pool_strdup(&tx->pool, target);
+ GITERR_CHECK_ALLOC(node->target.symbolic);
+ node->ref_type = GIT_REF_SYMBOLIC;
+
+ return 0;
+}
+
+int git_transaction_remove(git_transaction *tx, const char *refname)
+{
+ int error;
+ transaction_node *node;
+
+ if ((error = find_locked(&node, tx, refname)) < 0)
+ return error;
+
+ node->remove = true;
+ node->ref_type = GIT_REF_OID; /* the id will be ignored */
+
+ return 0;
+}
+
+static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool)
+{
+ git_reflog *reflog;
+ git_reflog_entry *entries;
+ size_t len, i;
+
+ reflog = git_pool_mallocz(pool, sizeof(git_reflog));
+ GITERR_CHECK_ALLOC(reflog);
+
+ reflog->ref_name = git_pool_strdup(pool, in->ref_name);
+ GITERR_CHECK_ALLOC(reflog->ref_name);
+
+ len = in->entries.length;
+ reflog->entries.length = len;
+ reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *));
+ GITERR_CHECK_ALLOC(reflog->entries.contents);
+
+ entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(entries);
+
+ for (i = 0; i < len; i++) {
+ const git_reflog_entry *src;
+ git_reflog_entry *tgt;
+
+ tgt = &entries[i];
+ reflog->entries.contents[i] = tgt;
+
+ src = git_vector_get(&in->entries, i);
+ git_oid_cpy(&tgt->oid_old, &src->oid_old);
+ git_oid_cpy(&tgt->oid_cur, &src->oid_cur);
+
+ tgt->msg = git_pool_strdup(pool, src->msg);
+ GITERR_CHECK_ALLOC(tgt->msg);
+
+ if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0)
+ return -1;
+ }
+
+
+ *out = reflog;
+ return 0;
+}
+
+int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog)
+{
+ int error;
+ transaction_node *node;
+
+ assert(tx && refname && reflog);
+
+ if ((error = find_locked(&node, tx, refname)) < 0)
+ return error;
+
+ if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0)
+ return error;
+
+ return 0;
+}
+
+static int update_target(git_refdb *db, transaction_node *node)
+{
+ git_reference *ref;
+ int error, update_reflog;
+
+ if (node->ref_type == GIT_REF_OID) {
+ ref = git_reference__alloc(node->name, &node->target.id, NULL);
+ } else if (node->ref_type == GIT_REF_SYMBOLIC) {
+ ref = git_reference__alloc_symbolic(node->name, node->target.symbolic);
+ } else {
+ abort();
+ }
+
+ GITERR_CHECK_ALLOC(ref);
+ update_reflog = node->reflog == NULL;
+
+ if (node->remove) {
+ error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL);
+ } else if (node->ref_type == GIT_REF_OID) {
+ error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
+ } else if (node->ref_type == GIT_REF_SYMBOLIC) {
+ error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
+ } else {
+ abort();
+ }
+
+ git_reference_free(ref);
+ node->committed = true;
+
+ return error;
+}
+
+int git_transaction_commit(git_transaction *tx)
+{
+ transaction_node *node;
+ git_strmap_iter pos;
+ int error = 0;
+
+ assert(tx);
+
+ if (tx->type == TRANSACTION_CONFIG) {
+ error = git_config_unlock(tx->cfg, true);
+ tx->cfg = NULL;
+
+ return error;
+ }
+
+ for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
+ if (!git_strmap_has_data(tx->locks, pos))
+ continue;
+
+ node = git_strmap_value_at(tx->locks, pos);
+ if (node->reflog) {
+ if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0)
+ return error;
+ }
+
+ if (node->ref_type != GIT_REF_INVALID) {
+ if ((error = update_target(tx->db, node)) < 0)
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+void git_transaction_free(git_transaction *tx)
+{
+ transaction_node *node;
+ git_pool pool;
+ git_strmap_iter pos;
+
+ assert(tx);
+
+ if (tx->type == TRANSACTION_CONFIG) {
+ if (tx->cfg) {
+ git_config_unlock(tx->cfg, false);
+ git_config_free(tx->cfg);
+ }
+
+ git__free(tx);
+ return;
+ }
+
+ /* start by unlocking the ones we've left hanging, if any */
+ for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
+ if (!git_strmap_has_data(tx->locks, pos))
+ continue;
+
+ node = git_strmap_value_at(tx->locks, pos);
+ if (node->committed)
+ continue;
+
+ git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
+ }
+
+ git_refdb_free(tx->db);
+ git_strmap_free(tx->locks);
+
+ /* tx is inside the pool, so we need to extract the data */
+ memcpy(&pool, &tx->pool, sizeof(git_pool));
+ git_pool_clear(&pool);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_transaction_h__
+#define INCLUDE_transaction_h__
+
+#include "common.h"
+
+int git_transaction_config_new(git_transaction **out, git_config *cfg);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/types.h"
+#include "git2/remote.h"
+#include "git2/net.h"
+#include "git2/transport.h"
+#include "git2/sys/transport.h"
+#include "path.h"
+
+typedef struct transport_definition {
+ char *prefix;
+ git_transport_cb fn;
+ void *param;
+} transport_definition;
+
+static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL };
+static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL };
+#ifdef GIT_SSH
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL };
+#endif
+
+static transport_definition local_transport_definition = { "file://", git_transport_local, NULL };
+
+static transport_definition transports[] = {
+ { "git://", git_transport_smart, &git_subtransport_definition },
+ { "http://", git_transport_smart, &http_subtransport_definition },
+ { "https://", git_transport_smart, &http_subtransport_definition },
+ { "file://", git_transport_local, NULL },
+#ifdef GIT_SSH
+ { "ssh://", git_transport_smart, &ssh_subtransport_definition },
+ { "ssh+git://", git_transport_smart, &ssh_subtransport_definition },
+ { "git+ssh://", git_transport_smart, &ssh_subtransport_definition },
+#endif
+ { NULL, 0, 0 }
+};
+
+static git_vector custom_transports = GIT_VECTOR_INIT;
+
+#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
+
+static transport_definition * transport_find_by_url(const char *url)
+{
+ size_t i = 0;
+ transport_definition *d;
+
+ /* Find a user transport who wants to deal with this URI */
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) {
+ return d;
+ }
+ }
+
+ /* Find a system transport for this URI */
+ for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
+ d = &transports[i];
+
+ if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) {
+ return d;
+ }
+ }
+
+ return NULL;
+}
+
+static int transport_find_fn(
+ git_transport_cb *out,
+ const char *url,
+ void **param)
+{
+ transport_definition *definition = transport_find_by_url(url);
+
+#ifdef GIT_WIN32
+ /* On Windows, it might not be possible to discern between absolute local
+ * and ssh paths - first check if this is a valid local path that points
+ * to a directory and if so assume local path, else assume SSH */
+
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+#endif
+
+ /* For other systems, perform the SSH check first, to avoid going to the
+ * filesystem if it is not necessary */
+
+ /* It could be a SSH remote path. Check to see if there's a : */
+ if (!definition && strrchr(url, ':')) {
+ /* re-search transports again with ssh:// as url
+ * so that we can find a third party ssh transport */
+ definition = transport_find_by_url("ssh://");
+ }
+
+#ifndef GIT_WIN32
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+#endif
+
+ if (!definition)
+ return GIT_ENOTFOUND;
+
+ *out = definition->fn;
+ *param = definition->param;
+
+ return 0;
+}
+
+/**************
+ * Public API *
+ **************/
+
+int git_transport_new(git_transport **out, git_remote *owner, const char *url)
+{
+ git_transport_cb fn;
+ git_transport *transport;
+ void *param;
+ int error;
+
+ if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) {
+ giterr_set(GITERR_NET, "Unsupported URL protocol");
+ return -1;
+ } else if (error < 0)
+ return error;
+
+ if ((error = fn(&transport, owner, param)) < 0)
+ return error;
+
+ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+
+ *out = transport;
+
+ return 0;
+}
+
+int git_transport_register(
+ const char *scheme,
+ git_transport_cb cb,
+ void *param)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ transport_definition *d, *definition = NULL;
+ size_t i;
+ int error = 0;
+
+ assert(scheme);
+ assert(cb);
+
+ if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strcasecmp(d->prefix, prefix.ptr) == 0) {
+ error = GIT_EEXISTS;
+ goto on_error;
+ }
+ }
+
+ definition = git__calloc(1, sizeof(transport_definition));
+ GITERR_CHECK_ALLOC(definition);
+
+ definition->prefix = git_buf_detach(&prefix);
+ definition->fn = cb;
+ definition->param = param;
+
+ if (git_vector_insert(&custom_transports, definition) < 0)
+ goto on_error;
+
+ return 0;
+
+on_error:
+ git_buf_free(&prefix);
+ git__free(definition);
+ return error;
+}
+
+int git_transport_unregister(const char *scheme)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ transport_definition *d;
+ size_t i;
+ int error = 0;
+
+ assert(scheme);
+
+ if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0)
+ goto done;
+
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strcasecmp(d->prefix, prefix.ptr) == 0) {
+ if ((error = git_vector_remove(&custom_transports, i)) < 0)
+ goto done;
+
+ git__free(d->prefix);
+ git__free(d);
+
+ if (!custom_transports.length)
+ git_vector_free(&custom_transports);
+
+ error = 0;
+ goto done;
+ }
+ }
+
+ error = GIT_ENOTFOUND;
+
+done:
+ git_buf_free(&prefix);
+ return error;
+}
+
+int git_transport_init(git_transport *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_transport, GIT_TRANSPORT_INIT);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "buffer.h"
+#include "auth.h"
+
+static int basic_next_token(
+ git_buf *out, git_http_auth_context *ctx, git_cred *c)
+{
+ git_cred_userpass_plaintext *cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
+
+ GIT_UNUSED(ctx);
+
+ if (c->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
+ giterr_set(GITERR_INVALID, "invalid credential type for basic auth");
+ goto on_error;
+ }
+
+ cred = (git_cred_userpass_plaintext *)c;
+
+ git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(out, "Authorization: Basic ") < 0 ||
+ git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 ||
+ git_buf_puts(out, "\r\n") < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (raw.size)
+ git__memzero(raw.ptr, raw.size);
+
+ git_buf_free(&raw);
+ return error;
+}
+
+static git_http_auth_context basic_context = {
+ GIT_AUTHTYPE_BASIC,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT,
+ NULL,
+ basic_next_token,
+ NULL
+};
+
+int git_http_auth_basic(
+ git_http_auth_context **out, const gitno_connection_data *connection_data)
+{
+ GIT_UNUSED(connection_data);
+
+ *out = &basic_context;
+ return 0;
+}
+
+int git_http_auth_dummy(
+ git_http_auth_context **out, const gitno_connection_data *connection_data)
+{
+ GIT_UNUSED(connection_data);
+
+ *out = NULL;
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_http_auth_h__
+#define INCLUDE_http_auth_h__
+
+#include "git2.h"
+#include "netops.h"
+
+typedef enum {
+ GIT_AUTHTYPE_BASIC = 1,
+ GIT_AUTHTYPE_NEGOTIATE = 2,
+} git_http_authtype_t;
+
+typedef struct git_http_auth_context git_http_auth_context;
+
+struct git_http_auth_context {
+ /** Type of scheme */
+ git_http_authtype_t type;
+
+ /** Supported credentials */
+ git_credtype_t credtypes;
+
+ /** Sets the challenge on the authentication context */
+ int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
+
+ /** Gets the next authentication token from the context */
+ int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred);
+
+ /** Frees the authentication context */
+ void (*free)(git_http_auth_context *ctx);
+};
+
+typedef struct {
+ /** Type of scheme */
+ git_http_authtype_t type;
+
+ /** Name of the scheme (as used in the Authorization header) */
+ const char *name;
+
+ /** Credential types this scheme supports */
+ git_credtype_t credtypes;
+
+ /** Function to initialize an authentication context */
+ int (*init_context)(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+} git_http_auth_scheme;
+
+int git_http_auth_dummy(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+int git_http_auth_basic(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_GSSAPI
+
+#include "git2.h"
+#include "common.h"
+#include "buffer.h"
+#include "auth.h"
+
+#include <gssapi.h>
+#include <krb5.h>
+
+static gss_OID_desc negotiate_oid_spnego =
+ { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
+static gss_OID_desc negotiate_oid_krb5 =
+ { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static gss_OID negotiate_oids[] =
+ { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
+
+typedef struct {
+ git_http_auth_context parent;
+ unsigned configured : 1,
+ complete : 1;
+ git_buf target;
+ char *challenge;
+ gss_ctx_id_t gss_context;
+ gss_OID oid;
+} http_auth_negotiate_context;
+
+static void negotiate_err_set(
+ OM_uint32 status_major,
+ OM_uint32 status_minor,
+ const char *message)
+{
+ gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
+ OM_uint32 status_display, context = 0;
+
+ if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
+ giterr_set(GITERR_NET, "%s: %.*s (%d.%d)",
+ message, (int)buffer.length, (const char *)buffer.value,
+ status_major, status_minor);
+ gss_release_buffer(&status_minor, &buffer);
+ } else {
+ giterr_set(GITERR_NET, "%s: unknown negotiate error (%d.%d)",
+ message, status_major, status_minor);
+ }
+}
+
+static int negotiate_set_challenge(
+ git_http_auth_context *c,
+ const char *challenge)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+
+ assert(ctx && ctx->configured && challenge);
+
+ git__free(ctx->challenge);
+
+ ctx->challenge = git__strdup(challenge);
+ GITERR_CHECK_ALLOC(ctx->challenge);
+
+ return 0;
+}
+
+static int negotiate_next_token(
+ git_buf *buf,
+ git_http_auth_context *c,
+ git_cred *cred)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ OM_uint32 status_major, status_minor;
+ gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
+ input_token = GSS_C_EMPTY_BUFFER,
+ output_token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
+ git_buf input_buf = GIT_BUF_INIT;
+ gss_name_t server = NULL;
+ gss_OID mech;
+ size_t challenge_len;
+ int error = 0;
+
+ assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDTYPE_DEFAULT);
+
+ if (ctx->complete)
+ return 0;
+
+ target_buffer.value = (void *)ctx->target.ptr;
+ target_buffer.length = ctx->target.size;
+
+ status_major = gss_import_name(&status_minor, &target_buffer,
+ GSS_C_NT_HOSTBASED_SERVICE, &server);
+
+ if (GSS_ERROR(status_major)) {
+ negotiate_err_set(status_major, status_minor,
+ "Could not parse principal");
+ error = -1;
+ goto done;
+ }
+
+ challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
+
+ if (challenge_len < 9) {
+ giterr_set(GITERR_NET, "No negotiate challenge sent from server");
+ error = -1;
+ goto done;
+ } else if (challenge_len > 9) {
+ if (git_buf_decode_base64(&input_buf,
+ ctx->challenge + 10, challenge_len - 10) < 0) {
+ giterr_set(GITERR_NET, "Invalid negotiate challenge from server");
+ error = -1;
+ goto done;
+ }
+
+ input_token.value = input_buf.ptr;
+ input_token.length = input_buf.size;
+ input_token_ptr = &input_token;
+ } else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
+ giterr_set(GITERR_NET, "Could not restart authentication");
+ error = -1;
+ goto done;
+ }
+
+ mech = &negotiate_oid_spnego;
+
+ if (GSS_ERROR(status_major = gss_init_sec_context(
+ &status_minor,
+ GSS_C_NO_CREDENTIAL,
+ &ctx->gss_context,
+ server,
+ mech,
+ GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ input_token_ptr,
+ NULL,
+ &output_token,
+ NULL,
+ NULL))) {
+ negotiate_err_set(status_major, status_minor, "Negotiate failure");
+ error = -1;
+ goto done;
+ }
+
+ /* This message merely told us auth was complete; we do not respond. */
+ if (status_major == GSS_S_COMPLETE) {
+ ctx->complete = 1;
+ goto done;
+ }
+
+ git_buf_puts(buf, "Authorization: Negotiate ");
+ git_buf_encode_base64(buf, output_token.value, output_token.length);
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ error = -1;
+
+done:
+ gss_release_name(&status_minor, &server);
+ gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
+ git_buf_free(&input_buf);
+ return error;
+}
+
+static void negotiate_context_free(git_http_auth_context *c)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ OM_uint32 status_minor;
+
+ if (ctx->gss_context != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(
+ &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
+ ctx->gss_context = GSS_C_NO_CONTEXT;
+ }
+
+ git_buf_free(&ctx->target);
+
+ git__free(ctx->challenge);
+
+ ctx->configured = 0;
+ ctx->complete = 0;
+ ctx->oid = NULL;
+
+ git__free(ctx);
+}
+
+static int negotiate_init_context(
+ http_auth_negotiate_context *ctx,
+ const gitno_connection_data *connection_data)
+{
+ OM_uint32 status_major, status_minor;
+ gss_OID item, *oid;
+ gss_OID_set mechanism_list;
+ size_t i;
+
+ /* Query supported mechanisms looking for SPNEGO) */
+ if (GSS_ERROR(status_major =
+ gss_indicate_mechs(&status_minor, &mechanism_list))) {
+ negotiate_err_set(status_major, status_minor,
+ "could not query mechanisms");
+ return -1;
+ }
+
+ if (mechanism_list) {
+ for (oid = negotiate_oids; *oid; oid++) {
+ for (i = 0; i < mechanism_list->count; i++) {
+ item = &mechanism_list->elements[i];
+
+ if (item->length == (*oid)->length &&
+ memcmp(item->elements, (*oid)->elements, item->length) == 0) {
+ ctx->oid = *oid;
+ break;
+ }
+
+ }
+
+ if (ctx->oid)
+ break;
+ }
+ }
+
+ gss_release_oid_set(&status_minor, &mechanism_list);
+
+ if (!ctx->oid) {
+ giterr_set(GITERR_NET, "Negotiate authentication is not supported");
+ return -1;
+ }
+
+ git_buf_puts(&ctx->target, "HTTP@");
+ git_buf_puts(&ctx->target, connection_data->host);
+
+ if (git_buf_oom(&ctx->target))
+ return -1;
+
+ ctx->gss_context = GSS_C_NO_CONTEXT;
+ ctx->configured = 1;
+
+ return 0;
+}
+
+int git_http_auth_negotiate(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data)
+{
+ http_auth_negotiate_context *ctx;
+
+ *out = NULL;
+
+ ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
+ GITERR_CHECK_ALLOC(ctx);
+
+ if (negotiate_init_context(ctx, connection_data) < 0) {
+ git__free(ctx);
+ return -1;
+ }
+
+ ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE;
+ ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT;
+ ctx->parent.set_challenge = negotiate_set_challenge;
+ ctx->parent.next_token = negotiate_next_token;
+ ctx->parent.free = negotiate_context_free;
+
+ *out = (git_http_auth_context *)ctx;
+
+ return 0;
+}
+
+#endif /* GIT_GSSAPI */
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_auth_negotiate_h__
+#define INCLUDE_auth_negotiate_h__
+
+#include "git2.h"
+#include "auth.h"
+
+#ifdef GIT_GSSAPI
+
+extern int git_http_auth_negotiate(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+#else
+
+#define git_http_auth_negotiate git_http_auth_dummy
+
+#endif /* GIT_GSSAPI */
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "smart.h"
+#include "git2/cred_helpers.h"
+
+static int git_cred_ssh_key_type_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase,
+ git_credtype_t credtype);
+
+int git_cred_has_username(git_cred *cred)
+{
+ if (cred->credtype == GIT_CREDTYPE_DEFAULT)
+ return 0;
+
+ return 1;
+}
+
+const char *git_cred__username(git_cred *cred)
+{
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERNAME:
+ {
+ git_cred_username *c = (git_cred_username *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT:
+ {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_KEY:
+ case GIT_CREDTYPE_SSH_MEMORY:
+ {
+ git_cred_ssh_key *c = (git_cred_ssh_key *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_CUSTOM:
+ {
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_INTERACTIVE:
+ {
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *) cred;
+ return c->username;
+ }
+
+ default:
+ return NULL;
+ }
+}
+
+static void plaintext_free(struct git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+
+ git__free(c->username);
+
+ /* Zero the memory which previously held the password */
+ if (c->password) {
+ size_t pass_len = strlen(c->password);
+ git__memzero(c->password, pass_len);
+ git__free(c->password);
+ }
+
+ git__free(c);
+}
+
+int git_cred_userpass_plaintext_new(
+ git_cred **cred,
+ const char *username,
+ const char *password)
+{
+ git_cred_userpass_plaintext *c;
+
+ assert(cred && username && password);
+
+ c = git__malloc(sizeof(git_cred_userpass_plaintext));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ c->parent.free = plaintext_free;
+ c->username = git__strdup(username);
+
+ if (!c->username) {
+ git__free(c);
+ return -1;
+ }
+
+ c->password = git__strdup(password);
+
+ if (!c->password) {
+ git__free(c->username);
+ git__free(c);
+ return -1;
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+static void ssh_key_free(struct git_cred *cred)
+{
+ git_cred_ssh_key *c =
+ (git_cred_ssh_key *)cred;
+
+ git__free(c->username);
+
+ if (c->privatekey) {
+ /* Zero the memory which previously held the private key */
+ size_t key_len = strlen(c->privatekey);
+ git__memzero(c->privatekey, key_len);
+ git__free(c->privatekey);
+ }
+
+ if (c->passphrase) {
+ /* Zero the memory which previously held the passphrase */
+ size_t pass_len = strlen(c->passphrase);
+ git__memzero(c->passphrase, pass_len);
+ git__free(c->passphrase);
+ }
+
+ if (c->publickey) {
+ /* Zero the memory which previously held the public key */
+ size_t key_len = strlen(c->publickey);
+ git__memzero(c->publickey, key_len);
+ git__free(c->publickey);
+ }
+
+ git__free(c);
+}
+
+static void ssh_interactive_free(struct git_cred *cred)
+{
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
+
+ git__free(c->username);
+
+ git__free(c);
+}
+
+static void ssh_custom_free(struct git_cred *cred)
+{
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
+
+ git__free(c->username);
+
+ if (c->publickey) {
+ /* Zero the memory which previously held the publickey */
+ size_t key_len = strlen(c->publickey);
+ git__memzero(c->publickey, key_len);
+ git__free(c->publickey);
+ }
+
+ git__free(c);
+}
+
+static void default_free(struct git_cred *cred)
+{
+ git_cred_default *c = (git_cred_default *)cred;
+
+ git__free(c);
+}
+
+static void username_free(struct git_cred *cred)
+{
+ git__free(cred);
+}
+
+int git_cred_ssh_key_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+ return git_cred_ssh_key_type_new(
+ cred,
+ username,
+ publickey,
+ privatekey,
+ passphrase,
+ GIT_CREDTYPE_SSH_KEY);
+}
+
+int git_cred_ssh_key_memory_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+#ifdef GIT_SSH_MEMORY_CREDENTIALS
+ return git_cred_ssh_key_type_new(
+ cred,
+ username,
+ publickey,
+ privatekey,
+ passphrase,
+ GIT_CREDTYPE_SSH_MEMORY);
+#else
+ GIT_UNUSED(cred);
+ GIT_UNUSED(username);
+ GIT_UNUSED(publickey);
+ GIT_UNUSED(privatekey);
+ GIT_UNUSED(passphrase);
+
+ giterr_set(GITERR_INVALID,
+ "This version of libgit2 was not built with ssh memory credentials.");
+ return -1;
+#endif
+}
+
+static int git_cred_ssh_key_type_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase,
+ git_credtype_t credtype)
+{
+ git_cred_ssh_key *c;
+
+ assert(username && cred && privatekey);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_key));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = credtype;
+ c->parent.free = ssh_key_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->privatekey = git__strdup(privatekey);
+ GITERR_CHECK_ALLOC(c->privatekey);
+
+ if (publickey) {
+ c->publickey = git__strdup(publickey);
+ GITERR_CHECK_ALLOC(c->publickey);
+ }
+
+ if (passphrase) {
+ c->passphrase = git__strdup(passphrase);
+ GITERR_CHECK_ALLOC(c->passphrase);
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_interactive_new(
+ git_cred **out,
+ const char *username,
+ git_cred_ssh_interactive_callback prompt_callback,
+ void *payload)
+{
+ git_cred_ssh_interactive *c;
+
+ assert(out && username && prompt_callback);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_interactive));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_INTERACTIVE;
+ c->parent.free = ssh_interactive_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->prompt_callback = prompt_callback;
+ c->payload = payload;
+
+ *out = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_key_from_agent(git_cred **cred, const char *username) {
+ git_cred_ssh_key *c;
+
+ assert(username && cred);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_key));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_KEY;
+ c->parent.free = ssh_key_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->privatekey = NULL;
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_custom_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ size_t publickey_len,
+ git_cred_sign_callback sign_callback,
+ void *payload)
+{
+ git_cred_ssh_custom *c;
+
+ assert(username && cred);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_custom));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_CUSTOM;
+ c->parent.free = ssh_custom_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ if (publickey_len > 0) {
+ c->publickey = git__malloc(publickey_len);
+ GITERR_CHECK_ALLOC(c->publickey);
+
+ memcpy(c->publickey, publickey, publickey_len);
+ }
+
+ c->publickey_len = publickey_len;
+ c->sign_callback = sign_callback;
+ c->payload = payload;
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_default_new(git_cred **cred)
+{
+ git_cred_default *c;
+
+ assert(cred);
+
+ c = git__calloc(1, sizeof(git_cred_default));
+ GITERR_CHECK_ALLOC(c);
+
+ c->credtype = GIT_CREDTYPE_DEFAULT;
+ c->free = default_free;
+
+ *cred = c;
+ return 0;
+}
+
+int git_cred_username_new(git_cred **cred, const char *username)
+{
+ git_cred_username *c;
+ size_t len, allocsize;
+
+ assert(cred);
+
+ len = strlen(username);
+
+ GITERR_CHECK_ALLOC_ADD(&allocsize, sizeof(git_cred_username), len);
+ GITERR_CHECK_ALLOC_ADD(&allocsize, allocsize, 1);
+ c = git__malloc(allocsize);
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERNAME;
+ c->parent.free = username_free;
+ memcpy(c->username, username, len + 1);
+
+ *cred = (git_cred *) c;
+ return 0;
+}
+
+void git_cred_free(git_cred *cred)
+{
+ if (!cred)
+ return;
+
+ cred->free(cred);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_cred_h__
+#define INCLUDE_git_cred_h__
+
+#include "git2/transport.h"
+
+const char *git_cred__username(git_cred *cred);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/cred_helpers.h"
+
+int git_cred_userpass(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;
+ const char *effective_username = NULL;
+
+ GIT_UNUSED(url);
+
+ if (!userpass || !userpass->password) return -1;
+
+ /* Username resolution: a username can be passed with the URL, the
+ * credentials payload, or both. Here's what we do. Note that if we get
+ * this far, we know that any password the url may contain has already
+ * failed at least once, so we ignore it.
+ *
+ * | Payload | URL | Used |
+ * +-------------+----------+-----------+
+ * | yes | no | payload |
+ * | yes | yes | payload |
+ * | no | yes | url |
+ * | no | no | FAIL |
+ */
+ if (userpass->username)
+ effective_username = userpass->username;
+ else if (user_from_url)
+ effective_username = user_from_url;
+ else
+ return -1;
+
+ if (GIT_CREDTYPE_USERNAME & allowed_types)
+ return git_cred_username_new(cred, effective_username);
+
+ if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
+ git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
+ return -1;
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+#include "git2/sys/transport.h"
+#include "stream.h"
+#include "socket_stream.h"
+
+#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git[] = "git://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ git_stream *io;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} git_proto_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+ git_proto_stream *current_stream;
+} git_subtransport;
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *delim, *repo;
+ char host[] = "host=";
+ size_t len;
+
+ delim = strchr(url, '/');
+ if (delim == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ repo = delim;
+ if (repo[1] == '~')
+ ++repo;
+
+ delim = strchr(url, ':');
+ if (delim == NULL)
+ delim = strchr(url, '/');
+
+ len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%04x%s %s%c%s",
+ (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host);
+ git_buf_put(request, url, delim - url);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(git_proto_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = git_stream_write(s->io, request.ptr, request.size, 0);
+ if (error >= 0)
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int git_proto_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int error;
+ git_proto_stream *s = (git_proto_stream *)stream;
+ gitno_buffer buf;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && (error = send_command(s)) < 0)
+ return error;
+
+ gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size);
+
+ if ((error = gitno_recv(&buf)) < 0)
+ return error;
+
+ *bytes_read = buf.offset;
+
+ return 0;
+}
+
+static int git_proto_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ int error;
+ git_proto_stream *s = (git_proto_stream *)stream;
+
+ if (!s->sent_command && (error = send_command(s)) < 0)
+ return error;
+
+ return git_stream_write(s->io, buffer, len, 0);
+}
+
+static void git_proto_stream_free(git_smart_subtransport_stream *stream)
+{
+ git_proto_stream *s;
+ git_subtransport *t;
+
+ if (!stream)
+ return;
+
+ s = (git_proto_stream *)stream;
+ t = OWNING_SUBTRANSPORT(s);
+
+ t->current_stream = NULL;
+
+ git_stream_close(s->io);
+ git_stream_free(s->io);
+ git__free(s->url);
+ git__free(s);
+}
+
+static int git_proto_stream_alloc(
+ git_subtransport *t,
+ const char *url,
+ const char *cmd,
+ const char *host,
+ const char *port,
+ git_smart_subtransport_stream **stream)
+{
+ git_proto_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(1, sizeof(git_proto_stream));
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_proto_stream_read;
+ s->parent.write = git_proto_stream_write;
+ s->parent.free = git_proto_stream_free;
+
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ if ((git_socket_stream_new(&s->io, host, port)) < 0)
+ return -1;
+
+ GITERR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream");
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int _git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *stream_url = url;
+ git_proto_stream *s;
+ int error;
+
+ *stream = NULL;
+
+ if (!git__prefixcmp(url, prefix_git))
+ stream_url += strlen(prefix_git);
+
+ if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
+ return error;
+
+ error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
+
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+
+
+ if (error < 0) {
+ git_proto_stream_free(*stream);
+ return error;
+ }
+
+ s = (git_proto_stream *) *stream;
+ if ((error = git_stream_connect(s->io)) < 0) {
+ git_proto_stream_free(*stream);
+ return error;
+ }
+
+ t->current_stream = s;
+
+ return 0;
+}
+
+static int _git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *stream_url = url;
+ git_proto_stream *s;
+ int error;
+
+ *stream = NULL;
+ if (!git__prefixcmp(url, prefix_git))
+ stream_url += strlen(prefix_git);
+
+ if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
+ return error;
+
+ error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream);
+
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+
+ if (error < 0) {
+ git_proto_stream_free(*stream);
+ return error;
+ }
+
+ s = (git_proto_stream *) *stream;
+
+ if ((error = git_stream_connect(s->io)) < 0)
+ return error;
+
+ t->current_stream = s;
+
+ return 0;
+}
+
+static int _git_receivepack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return _git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _git_free(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param)
+{
+ git_subtransport *t;
+
+ GIT_UNUSED(param);
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(1, sizeof(git_subtransport));
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.close = _git_close;
+ t->parent.free = _git_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef GIT_WINHTTP
+
+#include "git2.h"
+#include "http_parser.h"
+#include "buffer.h"
+#include "netops.h"
+#include "global.h"
+#include "remote.h"
+#include "smart.h"
+#include "auth.h"
+#include "auth_negotiate.h"
+#include "tls_stream.h"
+#include "socket_stream.h"
+#include "curl_stream.h"
+
+git_http_auth_scheme auth_schemes[] = {
+ { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
+ { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
+};
+
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const char *get_verb = "GET";
+static const char *post_verb = "POST";
+
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
+
+#define PARSE_ERROR_GENERIC -1
+#define PARSE_ERROR_REPLAY -2
+/** Look at the user field */
+#define PARSE_ERROR_EXT -3
+
+#define CHUNK_SIZE 4096
+
+enum last_cb {
+ NONE,
+ FIELD,
+ VALUE
+};
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ char *redirect_url;
+ const char *verb;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1,
+ redirect_count : 3;
+} http_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ git_stream *io;
+ gitno_connection_data connection_data;
+ bool connected;
+
+ /* Parser structures */
+ http_parser parser;
+ http_parser_settings settings;
+ gitno_buffer parse_buffer;
+ git_buf parse_header_name;
+ git_buf parse_header_value;
+ char parse_buffer_data[NETIO_BUFSIZE];
+ char *content_type;
+ char *location;
+ git_vector www_authenticate;
+ enum last_cb last_cb;
+ int parse_error;
+ int error;
+ unsigned parse_finished : 1;
+
+ /* Authentication */
+ git_cred *cred;
+ git_cred *url_cred;
+ git_vector auth_contexts;
+} http_subtransport;
+
+typedef struct {
+ http_stream *s;
+ http_subtransport *t;
+
+ /* Target buffer details from read() */
+ char *buffer;
+ size_t buf_size;
+ size_t *bytes_read;
+} parser_context;
+
+static bool credtype_match(git_http_auth_scheme *scheme, void *data)
+{
+ unsigned int credtype = *(unsigned int *)data;
+
+ return !!(scheme->credtypes & credtype);
+}
+
+static bool challenge_match(git_http_auth_scheme *scheme, void *data)
+{
+ const char *scheme_name = scheme->name;
+ const char *challenge = (const char *)data;
+ size_t scheme_len;
+
+ scheme_len = strlen(scheme_name);
+ return (strncasecmp(challenge, scheme_name, scheme_len) == 0 &&
+ (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
+}
+
+static int auth_context_match(
+ git_http_auth_context **out,
+ http_subtransport *t,
+ bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
+ void *data)
+{
+ git_http_auth_scheme *scheme = NULL;
+ git_http_auth_context *context = NULL, *c;
+ size_t i;
+
+ *out = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
+ if (scheme_match(&auth_schemes[i], data)) {
+ scheme = &auth_schemes[i];
+ break;
+ }
+ }
+
+ if (!scheme)
+ return 0;
+
+ /* See if authentication has already started for this scheme */
+ git_vector_foreach(&t->auth_contexts, i, c) {
+ if (c->type == scheme->type) {
+ context = c;
+ break;
+ }
+ }
+
+ if (!context) {
+ if (scheme->init_context(&context, &t->connection_data) < 0)
+ return -1;
+ else if (!context)
+ return 0;
+ else if (git_vector_insert(&t->auth_contexts, context) < 0)
+ return -1;
+ }
+
+ *out = context;
+
+ return 0;
+}
+
+static int apply_credentials(git_buf *buf, http_subtransport *t)
+{
+ git_cred *cred = t->cred;
+ git_http_auth_context *context;
+
+ /* Apply the credentials given to us in the URL */
+ if (!cred && t->connection_data.user && t->connection_data.pass) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred,
+ t->connection_data.user, t->connection_data.pass) < 0)
+ return -1;
+
+ cred = t->url_cred;
+ }
+
+ if (!cred)
+ return 0;
+
+ /* Get or create a context for the best scheme for this cred type */
+ if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
+ return -1;
+
+ return context->next_token(buf, context, cred);
+}
+
+static const char *user_agent(void)
+{
+ const char *custom = git_libgit2__user_agent();
+
+ if (custom)
+ return custom;
+
+ return "libgit2 " LIBGIT2_VERSION;
+}
+
+static int gen_request(
+ git_buf *buf,
+ http_stream *s,
+ size_t content_length)
+{
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ const char *path = t->connection_data.path ? t->connection_data.path : "/";
+ size_t i;
+
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
+
+ git_buf_printf(buf, "User-Agent: git/2.0 (%s)\r\n", user_agent());
+ git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
+
+ if (s->chunked || content_length > 0) {
+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
+
+ if (s->chunked)
+ git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
+ else
+ git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
+ } else
+ git_buf_puts(buf, "Accept: */*\r\n");
+
+ for (i = 0; i < t->owner->custom_headers.count; i++) {
+ if (t->owner->custom_headers.strings[i])
+ git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
+ }
+
+ /* Apply credentials to the request */
+ if (apply_credentials(buf, t) < 0)
+ return -1;
+
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ return -1;
+
+ return 0;
+}
+
+static int parse_authenticate_response(
+ git_vector *www_authenticate,
+ http_subtransport *t,
+ int *allowed_types)
+{
+ git_http_auth_context *context;
+ char *challenge;
+ size_t i;
+
+ git_vector_foreach(www_authenticate, i, challenge) {
+ if (auth_context_match(&context, t, challenge_match, challenge) < 0)
+ return -1;
+ else if (!context)
+ continue;
+
+ if (context->set_challenge &&
+ context->set_challenge(context, challenge) < 0)
+ return -1;
+
+ *allowed_types |= context->credtypes;
+ }
+
+ return 0;
+}
+
+static int on_header_ready(http_subtransport *t)
+{
+ git_buf *name = &t->parse_header_name;
+ git_buf *value = &t->parse_header_value;
+
+ if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ }
+ else if (!strcasecmp("WWW-Authenticate", git_buf_cstr(name))) {
+ char *dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
+
+ git_vector_insert(&t->www_authenticate, dup);
+ }
+ else if (!strcasecmp("Location", git_buf_cstr(name))) {
+ if (!t->location) {
+ t->location = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->location);
+ }
+ }
+
+ return 0;
+}
+
+static int on_header_field(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (NONE == t->last_cb || VALUE == t->last_cb)
+ git_buf_clear(&t->parse_header_name);
+
+ if (git_buf_put(&t->parse_header_name, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = FIELD;
+ return 0;
+}
+
+static int on_header_value(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ assert(NONE != t->last_cb);
+
+ if (FIELD == t->last_cb)
+ git_buf_clear(&t->parse_header_value);
+
+ if (git_buf_put(&t->parse_header_value, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = VALUE;
+ return 0;
+}
+
+static int on_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ http_stream *s = ctx->s;
+ git_buf buf = GIT_BUF_INIT;
+ int error = 0, no_callback = 0, allowed_auth_types = 0;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Capture authentication headers which may be a 401 (authentication
+ * is not complete) or a 200 (simply informing us that auth *is*
+ * complete.)
+ */
+ if (parse_authenticate_response(&t->www_authenticate, t,
+ &allowed_auth_types) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 && get_verb == s->verb) {
+ if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ if (allowed_auth_types) {
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ error = t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ t->connection_data.user,
+ allowed_auth_types,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH) {
+ no_callback = 1;
+ } else if (error < 0) {
+ t->error = error;
+ return t->parse_error = PARSE_ERROR_EXT;
+ } else {
+ assert(t->cred);
+
+ if (!(t->cred->credtype & allowed_auth_types)) {
+ giterr_set(GITERR_NET, "credentials callback returned an invalid cred type");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* Successfully acquired a credential. */
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+ }
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_NET, "authentication required but no callback set");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+ }
+
+ /* Check for a redirect.
+ * Right now we only permit a redirect to the same hostname. */
+ if ((parser->status_code == 301 ||
+ parser->status_code == 302 ||
+ (parser->status_code == 303 && get_verb == s->verb) ||
+ parser->status_code == 307) &&
+ t->location) {
+
+ if (s->redirect_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Set the redirect URL on the stream. This is a transfer of
+ * ownership of the memory. */
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ s->redirect_url = t->location;
+ t->location = NULL;
+
+ t->connected = 0;
+ s->redirect_count++;
+
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+ }
+
+ /* Check for a 200 HTTP status code. */
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET,
+ "Unexpected HTTP status code: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* The response must contain a Content-Type header. */
+ if (!t->content_type) {
+ giterr_set(GITERR_NET, "No Content-Type header in response");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* The Content-Type header must match our expectation. */
+ if (get_verb == s->verb)
+ git_buf_printf(&buf,
+ "application/x-git-%s-advertisement",
+ ctx->s->service);
+ else
+ git_buf_printf(&buf,
+ "application/x-git-%s-result",
+ ctx->s->service);
+
+ if (git_buf_oom(&buf))
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (strcmp(t->content_type, git_buf_cstr(&buf))) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_NET,
+ "Invalid Content-Type: %s",
+ t->content_type);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ git_buf_free(&buf);
+
+ return 0;
+}
+
+static int on_message_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ t->parse_finished = 1;
+
+ return 0;
+}
+
+static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ /* If our goal is to replay the request (either an auth failure or
+ * a redirect) then don't bother buffering since we're ignoring the
+ * content anyway.
+ */
+ if (t->parse_error == PARSE_ERROR_REPLAY)
+ return 0;
+
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "Can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ memcpy(ctx->buffer, str, len);
+ *(ctx->bytes_read) += len;
+ ctx->buffer += len;
+ ctx->buf_size -= len;
+
+ return 0;
+}
+
+static void clear_parser_state(http_subtransport *t)
+{
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ gitno_buffer_setup_fromstream(t->io,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
+
+ t->last_cb = NONE;
+ t->parse_error = 0;
+ t->parse_finished = 0;
+
+ git_buf_free(&t->parse_header_name);
+ git_buf_init(&t->parse_header_name, 0);
+
+ git_buf_free(&t->parse_header_value);
+ git_buf_init(&t->parse_header_value, 0);
+
+ git__free(t->content_type);
+ t->content_type = NULL;
+
+ git__free(t->location);
+ t->location = NULL;
+
+ git_vector_free_deep(&t->www_authenticate);
+}
+
+static int write_chunk(git_stream *io, const char *buffer, size_t len)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%" PRIxZ "\r\n", len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (git_stream_write(io, buf.ptr, buf.size, 0) < 0) {
+ git_buf_free(&buf);
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (len > 0 && git_stream_write(io, buffer, len, 0) < 0)
+ return -1;
+
+ /* Chunk footer */
+ if (git_stream_write(io, "\r\n", 2, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int apply_proxy_config(http_subtransport *t)
+{
+ int error;
+ git_proxy_t proxy_type;
+
+ if (!git_stream_supports_proxy(t->io))
+ return 0;
+
+ proxy_type = t->owner->proxy.type;
+
+ if (proxy_type == GIT_PROXY_NONE)
+ return 0;
+
+ if (proxy_type == GIT_PROXY_AUTO) {
+ char *url;
+ git_proxy_options opts = GIT_PROXY_OPTIONS_INIT;
+
+ if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
+ return error;
+
+ opts.type = GIT_PROXY_SPECIFIED;
+ opts.url = url;
+ error = git_stream_set_proxy(t->io, &opts);
+ git__free(url);
+
+ return error;
+ }
+
+ return git_stream_set_proxy(t->io, &t->owner->proxy);
+}
+
+static int http_connect(http_subtransport *t)
+{
+ int error;
+
+ if (t->connected &&
+ http_should_keep_alive(&t->parser) &&
+ t->parse_finished)
+ return 0;
+
+ if (t->io) {
+ git_stream_close(t->io);
+ git_stream_free(t->io);
+ t->io = NULL;
+ t->connected = 0;
+ }
+
+ if (t->connection_data.use_ssl) {
+ error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
+ } else {
+#ifdef GIT_CURL
+ error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
+#else
+ error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
+#endif
+ }
+
+ if (error < 0)
+ return error;
+
+ GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
+
+ apply_proxy_config(t);
+
+ error = git_stream_connect(t->io);
+
+ if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
+ git_stream_is_encrypted(t->io)) {
+ git_cert *cert;
+ int is_valid;
+
+ if ((error = git_stream_certificate(&cert, t->io)) < 0)
+ return error;
+
+ giterr_clear();
+ is_valid = error != GIT_ECERTIFICATE;
+ error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
+
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "user cancelled certificate check");
+
+ return error;
+ }
+ }
+
+ if (error < 0)
+ return error;
+
+ t->connected = 1;
+ return 0;
+}
+
+static int http_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ parser_context ctx;
+ size_t bytes_parsed;
+
+replay:
+ *bytes_read = 0;
+
+ assert(t->connected);
+
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, 0) < 0)
+ return -1;
+
+ if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
+ }
+
+ if (!s->received_response) {
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ while (!*bytes_read && !t->parse_finished) {
+ size_t data_offset;
+ int error;
+
+ /*
+ * Make the parse_buffer think it's as full of data as
+ * the buffer, so it won't try to recv more data than
+ * we can put into it.
+ *
+ * data_offset is the actual data offset from which we
+ * should tell the parser to start reading.
+ */
+ if (buf_size >= t->parse_buffer.len) {
+ t->parse_buffer.offset = 0;
+ } else {
+ t->parse_buffer.offset = t->parse_buffer.len - buf_size;
+ }
+
+ data_offset = t->parse_buffer.offset;
+
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
+
+ /* This call to http_parser_execute will result in invocations of the
+ * on_* family of callbacks. The most interesting of these is
+ * on_body_fill_buffer, which is called when data is ready to be copied
+ * into the target buffer. We need to marshal the buffer, buf_size, and
+ * bytes_read parameters to this callback. */
+ ctx.t = t;
+ ctx.s = s;
+ ctx.buffer = buffer;
+ ctx.buf_size = buf_size;
+ ctx.bytes_read = bytes_read;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ bytes_parsed = http_parser_execute(&t->parser,
+ &t->settings,
+ t->parse_buffer.data + data_offset,
+ t->parse_buffer.offset - data_offset);
+
+ t->parser.data = NULL;
+
+ /* If there was a handled authentication failure, then parse_error
+ * will have signaled us that we should replay the request. */
+ if (PARSE_ERROR_REPLAY == t->parse_error) {
+ s->sent_request = 0;
+
+ if ((error = http_connect(t)) < 0)
+ return error;
+
+ goto replay;
+ }
+
+ if (t->parse_error == PARSE_ERROR_EXT) {
+ return t->error;
+ }
+
+ if (t->parse_error < 0)
+ return -1;
+
+ if (bytes_parsed != t->parse_buffer.offset - data_offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int http_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ assert(t->connected);
+
+ /* Send the request, if necessary */
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, 0) < 0)
+ return -1;
+
+ if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
+ }
+
+ if (len > CHUNK_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(t->io, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CHUNK_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CHUNK_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int http_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
+
+ assert(t->connected);
+
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, len) < 0)
+ return -1;
+
+ if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
+ goto on_error;
+
+ if (len && git_stream_write(t->io, buffer, len, 0) < 0)
+ goto on_error;
+
+ git_buf_free(&request);
+ s->sent_request = 1;
+
+ return 0;
+
+on_error:
+ git_buf_free(&request);
+ return -1;
+}
+
+static void http_stream_free(git_smart_subtransport_stream *stream)
+{
+ http_stream *s = (http_stream *)stream;
+
+ if (s->chunk_buffer)
+ git__free(s->chunk_buffer);
+
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ git__free(s);
+}
+
+static int http_stream_alloc(http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(http_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = http_stream_read;
+ s->parent.write = http_stream_write_single;
+ s->parent.free = http_stream_free;
+
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
+}
+
+static int http_uploadpack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int http_uploadpack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int http_receivepack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int http_receivepack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ /* Use Transfer-Encoding: chunked for this request */
+ s->chunked = 1;
+ s->parent.write = http_stream_write_chunked;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int http_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *t = (http_subtransport *)subtransport;
+ int ret;
+
+ if (!stream)
+ return -1;
+
+ if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
+ (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
+ return ret;
+
+ if ((ret = http_connect(t)) < 0)
+ return ret;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return http_uploadpack_ls(t, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return http_uploadpack(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return http_receivepack_ls(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return http_receivepack(t, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int http_close(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+ git_http_auth_context *context;
+ size_t i;
+
+ clear_parser_state(t);
+
+ t->connected = 0;
+
+ if (t->io) {
+ git_stream_close(t->io);
+ git_stream_free(t->io);
+ t->io = NULL;
+ }
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
+ git_vector_foreach(&t->auth_contexts, i, context) {
+ if (context->free)
+ context->free(context);
+ }
+
+ git_vector_clear(&t->auth_contexts);
+
+ gitno_connection_data_free_ptrs(&t->connection_data);
+ memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ http_close(subtransport);
+
+ git_vector_free(&t->auth_contexts);
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
+{
+ http_subtransport *t;
+
+ GIT_UNUSED(param);
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(http_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = http_action;
+ t->parent.close = http_close;
+ t->parent.free = http_free;
+
+ t->settings.on_header_field = on_header_field;
+ t->settings.on_header_value = on_header_value;
+ t->settings.on_headers_complete = on_headers_complete;
+ t->settings.on_body = on_body_fill_buffer;
+ t->settings.on_message_complete = on_message_complete;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* !GIT_WINHTTP */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/types.h"
+#include "git2/net.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/revwalk.h"
+#include "git2/odb_backend.h"
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "pack-objects.h"
+#include "refs.h"
+#include "posix.h"
+#include "path.h"
+#include "buffer.h"
+#include "repository.h"
+#include "odb.h"
+#include "push.h"
+#include "remote.h"
+#include "proxy.h"
+
+typedef struct {
+ git_transport parent;
+ git_remote *owner;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
+ git_repository *repo;
+ git_transport_message_cb progress_cb;
+ git_transport_message_cb error_cb;
+ void *message_cb_payload;
+ git_vector refs;
+ unsigned connected : 1,
+ have_refs : 1;
+} transport_local;
+
+static void free_head(git_remote_head *head)
+{
+ git__free(head->name);
+ git__free(head->symref_target);
+ git__free(head);
+}
+
+static void free_heads(git_vector *heads)
+{
+ git_remote_head *head;
+ size_t i;
+
+ git_vector_foreach(heads, i, head)
+ free_head(head);
+
+ git_vector_free(heads);
+}
+
+static int add_ref(transport_local *t, const char *name)
+{
+ const char peeled[] = "^{}";
+ git_reference *ref, *resolved;
+ git_remote_head *head;
+ git_oid obj_id;
+ git_object *obj = NULL, *target = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_reference_lookup(&ref, t->repo, name)) < 0)
+ return error;
+
+ error = git_reference_resolve(&resolved, ref);
+ if (error < 0) {
+ git_reference_free(ref);
+ if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
+ /* This is actually okay. Empty repos often have a HEAD that
+ * points to a nonexistent "refs/heads/master". */
+ giterr_clear();
+ return 0;
+ }
+ return error;
+ }
+
+ git_oid_cpy(&obj_id, git_reference_target(resolved));
+ git_reference_free(resolved);
+
+ head = git__calloc(1, sizeof(git_remote_head));
+ GITERR_CHECK_ALLOC(head);
+
+ head->name = git__strdup(name);
+ GITERR_CHECK_ALLOC(head->name);
+
+ git_oid_cpy(&head->oid, &obj_id);
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ head->symref_target = git__strdup(git_reference_symbolic_target(ref));
+ GITERR_CHECK_ALLOC(head->symref_target);
+ }
+ git_reference_free(ref);
+
+ if ((error = git_vector_insert(&t->refs, head)) < 0) {
+ free_head(head);
+ return error;
+ }
+
+ /* If it's not a tag, we don't need to try to peel it */
+ if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return 0;
+
+ if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ head = NULL;
+
+ /* If it's not an annotated tag, or if we're mocking
+ * git-receive-pack, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG ||
+ t->direction != GIT_DIRECTION_FETCH) {
+ git_object_free(obj);
+ return 0;
+ }
+
+ /* And if it's a tag, peel it, and add it to the list */
+ head = git__calloc(1, sizeof(git_remote_head));
+ GITERR_CHECK_ALLOC(head);
+
+ if (git_buf_join(&buf, 0, name, peeled) < 0) {
+ free_head(head);
+ return -1;
+ }
+ head->name = git_buf_detach(&buf);
+
+ if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
+ git_oid_cpy(&head->oid, git_object_id(target));
+
+ if ((error = git_vector_insert(&t->refs, head)) < 0) {
+ free_head(head);
+ }
+ }
+
+ git_object_free(obj);
+ git_object_free(target);
+
+ return error;
+}
+
+static int store_refs(transport_local *t)
+{
+ size_t i;
+ git_remote_head *head;
+ git_strarray ref_names = {0};
+
+ assert(t);
+
+ if (git_reference_list(&ref_names, t->repo) < 0)
+ goto on_error;
+
+ /* Clear all heads we might have fetched in a previous connect */
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+
+ /* Clear the vector so we can reuse it */
+ git_vector_clear(&t->refs);
+
+ /* Sort the references first */
+ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
+
+ /* Add HEAD iff direction is fetch */
+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
+ goto on_error;
+
+ for (i = 0; i < ref_names.count; ++i) {
+ if (add_ref(t, ref_names.strings[i]) < 0)
+ goto on_error;
+ }
+
+ t->have_refs = 1;
+ git_strarray_free(&ref_names);
+ return 0;
+
+on_error:
+ git_vector_free(&t->refs);
+ git_strarray_free(&ref_names);
+ return -1;
+}
+
+/*
+ * Try to open the url as a git directory. The direction doesn't
+ * matter in this case because we're calculating the heads ourselves.
+ */
+static int local_connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ const git_proxy_options *proxy,
+ int direction, int flags)
+{
+ git_repository *repo;
+ int error;
+ transport_local *t = (transport_local *) transport;
+ const char *path;
+ git_buf buf = GIT_BUF_INIT;
+
+ GIT_UNUSED(cred_acquire_cb);
+ GIT_UNUSED(cred_acquire_payload);
+ GIT_UNUSED(proxy);
+
+ if (t->connected)
+ return 0;
+
+ free_heads(&t->refs);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+ t->direction = direction;
+ t->flags = flags;
+
+ /* 'url' may be a url or path; convert to a path */
+ if ((error = git_path_from_url_or_path(&buf, url)) < 0) {
+ git_buf_free(&buf);
+ return error;
+ }
+ path = git_buf_cstr(&buf);
+
+ error = git_repository_open(&repo, path);
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return -1;
+
+ t->repo = repo;
+
+ if (store_refs(t) < 0)
+ return -1;
+
+ t->connected = 1;
+
+ return 0;
+}
+
+static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ *out = (const git_remote_head **)t->refs.contents;
+ *size = t->refs.length;
+
+ return 0;
+}
+
+static int local_negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count)
+{
+ transport_local *t = (transport_local*)transport;
+ git_remote_head *rhead;
+ unsigned int i;
+
+ GIT_UNUSED(refs);
+ GIT_UNUSED(count);
+
+ /* Fill in the loids */
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+
+ int error = git_revparse_single(&obj, repo, rhead->name);
+ if (!error)
+ git_oid_cpy(&rhead->loid, git_object_id(obj));
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ else
+ giterr_clear();
+ git_object_free(obj);
+ }
+
+ return 0;
+}
+
+static int local_push_update_remote_ref(
+ git_repository *remote_repo,
+ const char *lref,
+ const char *rref,
+ git_oid *loid,
+ git_oid *roid)
+{
+ int error;
+ git_reference *remote_ref = NULL;
+
+ /* check for lhs, if it's empty it means to delete */
+ if (lref[0] != '\0') {
+ /* Create or update a ref */
+ error = git_reference_create(NULL, remote_repo, rref, loid,
+ !git_oid_iszero(roid), NULL);
+ } else {
+ /* Delete a ref */
+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ return error;
+ }
+
+ error = git_reference_delete(remote_ref);
+ git_reference_free(remote_ref);
+ }
+
+ return error;
+}
+
+static int transfer_to_push_transfer(const git_transfer_progress *stats, void *payload)
+{
+ const git_remote_callbacks *cbs = payload;
+
+ if (!cbs || !cbs->push_transfer_progress)
+ return 0;
+
+ return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes,
+ cbs->payload);
+}
+
+static int local_push(
+ git_transport *transport,
+ git_push *push,
+ const git_remote_callbacks *cbs)
+{
+ transport_local *t = (transport_local *)transport;
+ git_repository *remote_repo = NULL;
+ push_spec *spec;
+ char *url = NULL;
+ const char *path;
+ git_buf buf = GIT_BUF_INIT, odb_path = GIT_BUF_INIT;
+ int error;
+ size_t j;
+
+ GIT_UNUSED(cbs);
+
+ /* 'push->remote->url' may be a url or path; convert to a path */
+ if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) {
+ git_buf_free(&buf);
+ return error;
+ }
+ path = git_buf_cstr(&buf);
+
+ error = git_repository_open(&remote_repo, path);
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ /* We don't currently support pushing locally to non-bare repos. Proper
+ non-bare repo push support would require checking configs to see if
+ we should override the default 'don't let this happen' behavior.
+
+ Note that this is only an issue when pushing to the current branch,
+ but we forbid all pushes just in case */
+ if (!remote_repo->is_bare) {
+ error = GIT_EBAREREPO;
+ giterr_set(GITERR_INVALID, "Local push doesn't (yet) support pushing to non-bare repos.");
+ goto on_error;
+ }
+
+ if ((error = git_buf_joinpath(&odb_path, git_repository_path(remote_repo), "objects/pack")) < 0)
+ goto on_error;
+
+ error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
+ git_buf_free(&odb_path);
+
+ if (error < 0)
+ goto on_error;
+
+ push->unpack_ok = 1;
+
+ git_vector_foreach(&push->specs, j, spec) {
+ push_status *status;
+ const git_error *last;
+ char *ref = spec->refspec.dst;
+
+ status = git__calloc(1, sizeof(push_status));
+ if (!status)
+ goto on_error;
+
+ status->ref = git__strdup(ref);
+ if (!status->ref) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst,
+ &spec->loid, &spec->roid);
+
+ switch (error) {
+ case GIT_OK:
+ break;
+ case GIT_EINVALIDSPEC:
+ status->msg = git__strdup("funny refname");
+ break;
+ case GIT_ENOTFOUND:
+ status->msg = git__strdup("Remote branch not found to delete");
+ break;
+ default:
+ last = giterr_last();
+
+ if (last && last->message)
+ status->msg = git__strdup(last->message);
+ else
+ status->msg = git__strdup("Unspecified error encountered");
+ break;
+ }
+
+ /* failed to allocate memory for a status message */
+ if (error < 0 && !status->msg) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ /* failed to insert the ref update status */
+ if ((error = git_vector_insert(&push->status, status)) < 0) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+ }
+
+ if (push->specs.length) {
+ int flags = t->flags;
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url,
+ NULL, NULL, NULL, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_repository_free(remote_repo);
+ git__free(url);
+
+ return error;
+}
+
+typedef struct foreach_data {
+ git_transfer_progress *stats;
+ git_transfer_progress_cb progress_cb;
+ void *progress_payload;
+ git_odb_writepack *writepack;
+} foreach_data;
+
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ foreach_data *data = (foreach_data*)payload;
+
+ data->stats->received_bytes += len;
+ return data->writepack->append(data->writepack, buf, len, data->stats);
+}
+
+static const char *counting_objects_fmt = "Counting objects %d\r";
+static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)";
+
+static int local_counting(int stage, unsigned int current, unsigned int total, void *payload)
+{
+ git_buf progress_info = GIT_BUF_INIT;
+ transport_local *t = payload;
+ int error;
+
+ if (!t->progress_cb)
+ return 0;
+
+ if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) {
+ git_buf_printf(&progress_info, counting_objects_fmt, current);
+ } else if (stage == GIT_PACKBUILDER_DELTAFICATION) {
+ float perc = (((float) current) / total) * 100;
+ git_buf_printf(&progress_info, compressing_objects_fmt, perc, current, total);
+ if (current == total)
+ git_buf_printf(&progress_info, ", done\n");
+ else
+ git_buf_putc(&progress_info, '\r');
+
+ }
+
+ if (git_buf_oom(&progress_info))
+ return -1;
+
+ error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload);
+ git_buf_free(&progress_info);
+
+ return error;
+}
+
+static int local_download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload)
+{
+ transport_local *t = (transport_local*)transport;
+ git_revwalk *walk = NULL;
+ git_remote_head *rhead;
+ unsigned int i;
+ int error = -1;
+ git_packbuilder *pack = NULL;
+ git_odb_writepack *writepack = NULL;
+ git_odb *odb = NULL;
+ git_buf progress_info = GIT_BUF_INIT;
+
+ if ((error = git_revwalk_new(&walk, t->repo)) < 0)
+ goto cleanup;
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
+ goto cleanup;
+
+ git_packbuilder_set_callbacks(pack, local_counting, t);
+
+ stats->total_objects = 0;
+ stats->indexed_objects = 0;
+ stats->received_objects = 0;
+ stats->received_bytes = 0;
+
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if (git_object_type(obj) == GIT_OBJ_COMMIT) {
+ /* Revwalker includes only wanted commits */
+ error = git_revwalk_push(walk, &rhead->oid);
+ if (!error && !git_oid_iszero(&rhead->loid)) {
+ error = git_revwalk_hide(walk, &rhead->loid);
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ }
+ } else {
+ /* Tag or some other wanted object. Add it on its own */
+ error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name);
+ }
+ git_object_free(obj);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_packbuilder_insert_walk(pack, walk)))
+ goto cleanup;
+
+ if ((error = git_buf_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack))) < 0)
+ goto cleanup;
+
+ if (t->progress_cb &&
+ (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0)
+ goto cleanup;
+
+ /* Walk the objects, building a packfile */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ /* One last one with the newline */
+ git_buf_clear(&progress_info);
+ git_buf_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack));
+ if ((error = git_buf_putc(&progress_info, '\n')) < 0)
+ goto cleanup;
+
+ if (t->progress_cb &&
+ (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0)
+ goto cleanup;
+
+ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)
+ goto cleanup;
+
+ /* Write the data to the ODB */
+ {
+ foreach_data data = {0};
+ data.stats = stats;
+ data.progress_cb = progress_cb;
+ data.progress_payload = progress_payload;
+ data.writepack = writepack;
+
+ /* autodetect */
+ git_packbuilder_set_threads(pack, 0);
+
+ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
+ goto cleanup;
+ }
+
+ error = writepack->commit(writepack, stats);
+
+cleanup:
+ if (writepack) writepack->free(writepack);
+ git_buf_free(&progress_info);
+ git_packbuilder_free(pack);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int local_set_callbacks(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
+ void *message_cb_payload)
+{
+ transport_local *t = (transport_local *)transport;
+
+ GIT_UNUSED(certificate_check_cb);
+
+ t->progress_cb = progress_cb;
+ t->error_cb = error_cb;
+ t->message_cb_payload = message_cb_payload;
+
+ return 0;
+}
+
+static int local_is_connected(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ return t->connected;
+}
+
+static int local_read_flags(git_transport *transport, int *flags)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static void local_cancel(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int local_close(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ t->connected = 0;
+
+ if (t->repo) {
+ git_repository_free(t->repo);
+ t->repo = NULL;
+ }
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
+
+ return 0;
+}
+
+static void local_free(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ free_heads(&t->refs);
+
+ /* Close the transport, if it's still open. */
+ local_close(transport);
+
+ /* Free the transport */
+ git__free(t);
+}
+
+/**************
+ * Public API *
+ **************/
+
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
+{
+ int error;
+ transport_local *t;
+
+ GIT_UNUSED(param);
+
+ t = git__calloc(1, sizeof(transport_local));
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.version = GIT_TRANSPORT_VERSION;
+ t->parent.set_callbacks = local_set_callbacks;
+ t->parent.connect = local_connect;
+ t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
+ t->parent.push = local_push;
+ t->parent.close = local_close;
+ t->parent.free = local_free;
+ t->parent.ls = local_ls;
+ t->parent.is_connected = local_is_connected;
+ t->parent.read_flags = local_read_flags;
+ t->parent.cancel = local_cancel;
+
+ if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) {
+ git__free(t);
+ return error;
+ }
+
+ t->owner = owner;
+
+ *out = (git_transport *) t;
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "smart.h"
+#include "refs.h"
+#include "refspec.h"
+#include "proxy.h"
+
+static int git_smart__recv_cb(gitno_buffer *buf)
+{
+ transport_smart *t = (transport_smart *) buf->cb_data;
+ size_t old_len, bytes_read;
+ int error;
+
+ assert(t->current_stream);
+
+ old_len = buf->offset;
+
+ if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
+ return error;
+
+ buf->offset += bytes_read;
+
+ if (t->packetsize_cb && !t->cancelled.val) {
+ error = t->packetsize_cb(bytes_read, t->packetsize_payload);
+ if (error) {
+ git_atomic_set(&t->cancelled, 1);
+ return GIT_EUSER;
+ }
+ }
+
+ return (int)(buf->offset - old_len);
+}
+
+GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
+{
+ if (t->current_stream) {
+ t->current_stream->free(t->current_stream);
+ t->current_stream = NULL;
+ }
+
+ if (close_subtransport &&
+ t->wrapped->close(t->wrapped) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int git_smart__set_callbacks(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
+ void *message_cb_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ t->progress_cb = progress_cb;
+ t->error_cb = error_cb;
+ t->certificate_check_cb = certificate_check_cb;
+ t->message_cb_payload = message_cb_payload;
+
+ return 0;
+}
+
+static int http_header_name_length(const char *http_header)
+{
+ const char *colon = strchr(http_header, ':');
+ if (!colon)
+ return 0;
+ return colon - http_header;
+}
+
+static bool is_malformed_http_header(const char *http_header)
+{
+ const char *c;
+ int name_len;
+
+ // Disallow \r and \n
+ c = strchr(http_header, '\r');
+ if (c)
+ return true;
+ c = strchr(http_header, '\n');
+ if (c)
+ return true;
+
+ // Require a header name followed by :
+ name_len = http_header_name_length(http_header);
+ if (name_len < 1)
+ return true;
+
+ return false;
+}
+
+static char *forbidden_custom_headers[] = {
+ "User-Agent",
+ "Host",
+ "Accept",
+ "Content-Type",
+ "Transfer-Encoding",
+ "Content-Length",
+};
+
+static bool is_forbidden_custom_header(const char *custom_header)
+{
+ unsigned long i;
+ int name_len = http_header_name_length(custom_header);
+
+ // Disallow headers that we set
+ for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++)
+ if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0)
+ return true;
+
+ return false;
+}
+
+static int git_smart__set_custom_headers(
+ git_transport *transport,
+ const git_strarray *custom_headers)
+{
+ transport_smart *t = (transport_smart *)transport;
+ size_t i;
+
+ if (t->custom_headers.count)
+ git_strarray_free(&t->custom_headers);
+
+ if (!custom_headers)
+ return 0;
+
+ for (i = 0; i < custom_headers->count; i++) {
+ if (is_malformed_http_header(custom_headers->strings[i])) {
+ giterr_set(GITERR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]);
+ return -1;
+ }
+ if (is_forbidden_custom_header(custom_headers->strings[i])) {
+ giterr_set(GITERR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]);
+ return -1;
+ }
+ }
+
+ return git_strarray_copy(&t->custom_headers, custom_headers);
+}
+
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
+{
+ size_t i;
+ git_pkt *pkt;
+
+ git_vector_clear(&t->heads);
+ git_vector_foreach(&t->refs, i, pkt) {
+ git_pkt_ref *ref = (git_pkt_ref *) pkt;
+ if (pkt->type != GIT_PKT_REF)
+ continue;
+
+ if (symrefs) {
+ git_refspec *spec;
+ git_buf buf = GIT_BUF_INIT;
+ size_t j;
+ int error = 0;
+
+ git_vector_foreach(symrefs, j, spec) {
+ git_buf_clear(&buf);
+ if (git_refspec_src_matches(spec, ref->head.name) &&
+ !(error = git_refspec_transform(&buf, spec, ref->head.name)))
+ ref->head.symref_target = git_buf_detach(&buf);
+ }
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+ }
+
+ if (git_vector_insert(&t->heads, &ref->head) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_symrefs(git_vector *symrefs)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(symrefs, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+
+ git_vector_free(symrefs);
+}
+
+static int git_smart__connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ const git_proxy_options *proxy,
+ int direction,
+ int flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+ git_pkt *pkt;
+ git_pkt_ref *first;
+ git_vector symrefs;
+ git_smart_service_t service;
+
+ if (git_smart__reset_stream(t, true) < 0)
+ return -1;
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+
+ if (git_proxy_options_dup(&t->proxy, proxy) < 0)
+ return -1;
+
+ t->direction = direction;
+ t->flags = flags;
+ t->cred_acquire_cb = cred_acquire_cb;
+ t->cred_acquire_payload = cred_acquire_payload;
+
+ if (GIT_DIRECTION_FETCH == t->direction)
+ service = GIT_SERVICE_UPLOADPACK_LS;
+ else if (GIT_DIRECTION_PUSH == t->direction)
+ service = GIT_SERVICE_RECEIVEPACK_LS;
+ else {
+ giterr_set(GITERR_NET, "Invalid direction");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
+ return error;
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ /* 2 flushes for RPC; 1 for stateful */
+ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
+ return error;
+
+ /* Strip the comment packet for RPC */
+ if (t->rpc) {
+ pkt = (git_pkt *)git_vector_get(&t->refs, 0);
+
+ if (!pkt || GIT_PKT_COMMENT != pkt->type) {
+ giterr_set(GITERR_NET, "Invalid response");
+ return -1;
+ } else {
+ /* Remove the comment pkt from the list */
+ git_vector_remove(&t->refs, 0);
+ git__free(pkt);
+ }
+ }
+
+ /* We now have loaded the refs. */
+ t->have_refs = 1;
+
+ first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
+
+ if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
+ return error;
+
+ /* Detect capabilities */
+ if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0)
+ return -1;
+
+ /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
+ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
+ git_oid_iszero(&first->head.oid)) {
+ git_vector_clear(&t->refs);
+ git_pkt_free((git_pkt *)first);
+ }
+
+ /* Keep a list of heads for _ls */
+ git_smart__update_heads(t, &symrefs);
+
+ free_symrefs(&symrefs);
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
+}
+
+static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ *out = (const git_remote_head **) t->heads.contents;
+ *size = t->heads.length;
+
+ return 0;
+}
+
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_FETCH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for fetch");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ if ((error = stream->write(stream, (const char *)data, len)) < 0)
+ return error;
+
+ gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
+{
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_PUSH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for push");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == *stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = *stream;
+
+ gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+static void git_smart__cancel(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int git_smart__is_connected(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ return t->connected;
+}
+
+static int git_smart__read_flags(git_transport *transport, int *flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static int git_smart__close(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+ int ret;
+ git_smart_subtransport_stream *stream;
+ const char flush[] = "0000";
+
+ /*
+ * If we're still connected at this point and not using RPC,
+ * we should say goodbye by sending a flush, or git-daemon
+ * will complain that we disconnected unexpectedly.
+ */
+ if (t->connected && !t->rpc &&
+ !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
+ t->current_stream->write(t->current_stream, flush, 4);
+ }
+
+ ret = git_smart__reset_stream(t, true);
+
+ git_vector_foreach(common, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(common);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
+
+ t->connected = 0;
+
+ return ret;
+}
+
+static void git_smart__free(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+ git_pkt *p;
+
+ /* Make sure that the current stream is closed, if we have one. */
+ git_smart__close(transport);
+
+ /* Free the subtransport */
+ t->wrapped->free(t->wrapped);
+
+ git_vector_free(&t->heads);
+ git_vector_foreach(refs, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(refs);
+ git__free((char *)t->proxy.url);
+
+ git_strarray_free(&t->custom_headers);
+
+ git__free(t);
+}
+
+static int ref_name_cmp(const void *a, const void *b)
+{
+ const git_pkt_ref *ref_a = a, *ref_b = b;
+
+ return strcmp(ref_a->head.name, ref_b->head.name);
+}
+
+int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload);
+}
+
+int git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload);
+}
+
+int git_transport_smart(git_transport **out, git_remote *owner, void *param)
+{
+ transport_smart *t;
+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
+
+ if (!param)
+ return -1;
+
+ t = git__calloc(1, sizeof(transport_smart));
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.version = GIT_TRANSPORT_VERSION;
+ t->parent.set_callbacks = git_smart__set_callbacks;
+ t->parent.set_custom_headers = git_smart__set_custom_headers;
+ t->parent.connect = git_smart__connect;
+ t->parent.close = git_smart__close;
+ t->parent.free = git_smart__free;
+ t->parent.negotiate_fetch = git_smart__negotiate_fetch;
+ t->parent.download_pack = git_smart__download_pack;
+ t->parent.push = git_smart__push;
+ t->parent.ls = git_smart__ls;
+ t->parent.is_connected = git_smart__is_connected;
+ t->parent.read_flags = git_smart__read_flags;
+ t->parent.cancel = git_smart__cancel;
+
+ t->owner = owner;
+ t->rpc = definition->rpc;
+
+ if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ *out = (git_transport *) t;
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "vector.h"
+#include "netops.h"
+#include "buffer.h"
+#include "push.h"
+#include "git2/sys/transport.h"
+
+#define GIT_SIDE_BAND_DATA 1
+#define GIT_SIDE_BAND_PROGRESS 2
+#define GIT_SIDE_BAND_ERROR 3
+
+#define GIT_CAP_OFS_DELTA "ofs-delta"
+#define GIT_CAP_MULTI_ACK "multi_ack"
+#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed"
+#define GIT_CAP_SIDE_BAND "side-band"
+#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
+#define GIT_CAP_DELETE_REFS "delete-refs"
+#define GIT_CAP_REPORT_STATUS "report-status"
+#define GIT_CAP_THIN_PACK "thin-pack"
+#define GIT_CAP_SYMREF "symref"
+
+enum git_pkt_type {
+ GIT_PKT_CMD,
+ GIT_PKT_FLUSH,
+ GIT_PKT_REF,
+ GIT_PKT_HAVE,
+ GIT_PKT_ACK,
+ GIT_PKT_NAK,
+ GIT_PKT_PACK,
+ GIT_PKT_COMMENT,
+ GIT_PKT_ERR,
+ GIT_PKT_DATA,
+ GIT_PKT_PROGRESS,
+ GIT_PKT_OK,
+ GIT_PKT_NG,
+ GIT_PKT_UNPACK,
+};
+
+/* Used for multi_ack and mutli_ack_detailed */
+enum git_ack_status {
+ GIT_ACK_NONE,
+ GIT_ACK_CONTINUE,
+ GIT_ACK_COMMON,
+ GIT_ACK_READY
+};
+
+/* This would be a flush pkt */
+typedef struct {
+ enum git_pkt_type type;
+} git_pkt;
+
+struct git_pkt_cmd {
+ enum git_pkt_type type;
+ char *cmd;
+ char *path;
+ char *host;
+};
+
+/* This is a pkt-line with some info in it */
+typedef struct {
+ enum git_pkt_type type;
+ git_remote_head head;
+ char *capabilities;
+} git_pkt_ref;
+
+/* Useful later */
+typedef struct {
+ enum git_pkt_type type;
+ git_oid oid;
+ enum git_ack_status status;
+} git_pkt_ack;
+
+typedef struct {
+ enum git_pkt_type type;
+ char comment[GIT_FLEX_ARRAY];
+} git_pkt_comment;
+
+typedef struct {
+ enum git_pkt_type type;
+ int len;
+ char data[GIT_FLEX_ARRAY];
+} git_pkt_data;
+
+typedef git_pkt_data git_pkt_progress;
+
+typedef struct {
+ enum git_pkt_type type;
+ int len;
+ char error[GIT_FLEX_ARRAY];
+} git_pkt_err;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+} git_pkt_ok;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+ char *msg;
+} git_pkt_ng;
+
+typedef struct {
+ enum git_pkt_type type;
+ int unpack_ok;
+} git_pkt_unpack;
+
+typedef struct transport_smart_caps {
+ int common:1,
+ ofs_delta:1,
+ multi_ack: 1,
+ multi_ack_detailed: 1,
+ side_band:1,
+ side_band_64k:1,
+ include_tag:1,
+ delete_refs:1,
+ report_status:1,
+ thin_pack:1;
+} transport_smart_caps;
+
+typedef int (*packetsize_cb)(size_t received, void *payload);
+
+typedef struct {
+ git_transport parent;
+ git_remote *owner;
+ char *url;
+ git_cred_acquire_cb cred_acquire_cb;
+ void *cred_acquire_payload;
+ git_proxy_options proxy;
+ int direction;
+ int flags;
+ git_transport_message_cb progress_cb;
+ git_transport_message_cb error_cb;
+ git_transport_certificate_check_cb certificate_check_cb;
+ void *message_cb_payload;
+ git_strarray custom_headers;
+ git_smart_subtransport *wrapped;
+ git_smart_subtransport_stream *current_stream;
+ transport_smart_caps caps;
+ git_vector refs;
+ git_vector heads;
+ git_vector common;
+ git_atomic cancelled;
+ packetsize_cb packetsize_cb;
+ void *packetsize_payload;
+ unsigned rpc : 1,
+ have_refs : 1,
+ connected : 1;
+ gitno_buffer buffer;
+ char buffer_data[65536];
+} transport_smart;
+
+/* smart_protocol.c */
+int git_smart__store_refs(transport_smart *t, int flushes);
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs);
+int git_smart__push(git_transport *transport, git_push *push, const git_remote_callbacks *cbs);
+
+int git_smart__negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count);
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload);
+
+/* smart.c */
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
+
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs);
+
+/* smart_pkt.c */
+int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
+int git_pkt_buffer_flush(git_buf *buf);
+int git_pkt_send_flush(GIT_SOCKET s);
+int git_pkt_buffer_done(git_buf *buf);
+int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf);
+int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
+void git_pkt_free(git_pkt *pkt);
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+
+#include "git2/types.h"
+#include "git2/errors.h"
+#include "git2/refs.h"
+#include "git2/revwalk.h"
+
+#include "smart.h"
+#include "util.h"
+#include "netops.h"
+#include "posix.h"
+#include "buffer.h"
+
+#include <ctype.h>
+
+#define PKT_LEN_SIZE 4
+static const char pkt_done_str[] = "0009done\n";
+static const char pkt_flush_str[] = "0000";
+static const char pkt_have_prefix[] = "0032have ";
+static const char pkt_want_prefix[] = "0032want ";
+
+static int flush_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_FLUSH;
+ *out = pkt;
+
+ return 0;
+}
+
+/* the rest of the line will be useful for multi_ack and multi_ack_detailed */
+static int ack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ack *pkt;
+ GIT_UNUSED(line);
+ GIT_UNUSED(len);
+
+ pkt = git__calloc(1, sizeof(git_pkt_ack));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ACK;
+ line += 3;
+ len -= 3;
+
+ if (len >= GIT_OID_HEXSZ) {
+ git_oid_fromstr(&pkt->oid, line + 1);
+ line += GIT_OID_HEXSZ + 1;
+ len -= GIT_OID_HEXSZ + 1;
+ }
+
+ if (len >= 7) {
+ if (!git__prefixcmp(line + 1, "continue"))
+ pkt->status = GIT_ACK_CONTINUE;
+ if (!git__prefixcmp(line + 1, "common"))
+ pkt->status = GIT_ACK_COMMON;
+ if (!git__prefixcmp(line + 1, "ready"))
+ pkt->status = GIT_ACK_READY;
+ }
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int nak_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_NAK;
+ *out = pkt;
+
+ return 0;
+}
+
+static int pack_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_PACK;
+ *out = pkt;
+
+ return 0;
+}
+
+static int comment_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_comment *pkt;
+ size_t alloclen;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ pkt = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_COMMENT;
+ memcpy(pkt->comment, line, len);
+ pkt->comment[len] = '\0';
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int err_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+ size_t alloclen;
+
+ /* Remove "ERR " from the line */
+ line += 4;
+ len -= 4;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
+ GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ pkt = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int data_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_data *pkt;
+ size_t alloclen;
+
+ line++;
+ len--;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
+ pkt = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_DATA;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_progress *pkt;
+ size_t alloclen;
+
+ line++;
+ len--;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
+ pkt = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_PROGRESS;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+ size_t alloc_len;
+
+ line++;
+ len--;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len);
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+ pkt = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+
+ return 0;
+}
+
+/*
+ * Parse an other-ref line.
+ */
+static int ref_pkt(git_pkt **out, const char *line, size_t len)
+{
+ int error;
+ git_pkt_ref *pkt;
+ size_t alloclen;
+
+ pkt = git__malloc(sizeof(git_pkt_ref));
+ GITERR_CHECK_ALLOC(pkt);
+
+ memset(pkt, 0x0, sizeof(git_pkt_ref));
+ pkt->type = GIT_PKT_REF;
+ if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0)
+ goto error_out;
+
+ /* Check for a bit of consistency */
+ if (line[GIT_OID_HEXSZ] != ' ') {
+ giterr_set(GITERR_NET, "Error parsing pkt-line");
+ error = -1;
+ goto error_out;
+ }
+
+ /* Jump from the name */
+ line += GIT_OID_HEXSZ + 1;
+ len -= (GIT_OID_HEXSZ + 1);
+
+ if (line[len - 1] == '\n')
+ --len;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+ pkt->head.name = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt->head.name);
+
+ memcpy(pkt->head.name, line, len);
+ pkt->head.name[len] = '\0';
+
+ if (strlen(pkt->head.name) < len) {
+ pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
+ }
+
+ *out = (git_pkt *)pkt;
+ return 0;
+
+error_out:
+ git__free(pkt);
+ return error;
+}
+
+static int ok_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ok *pkt;
+ const char *ptr;
+ size_t alloc_len;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_OK;
+
+ line += 3; /* skip "ok " */
+ if (!(ptr = strchr(line, '\n'))) {
+ giterr_set(GITERR_NET, "Invalid packet line");
+ git__free(pkt);
+ return -1;
+ }
+ len = ptr - line;
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
+ pkt->ref = git__malloc(alloc_len);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int ng_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ng *pkt;
+ const char *ptr;
+ size_t alloclen;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->ref = NULL;
+ pkt->type = GIT_PKT_NG;
+
+ line += 3; /* skip "ng " */
+ if (!(ptr = strchr(line, ' ')))
+ goto out_err;
+ len = ptr - line;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+ pkt->ref = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ line = ptr + 1;
+ if (!(ptr = strchr(line, '\n')))
+ goto out_err;
+ len = ptr - line;
+
+ GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+ pkt->msg = git__malloc(alloclen);
+ GITERR_CHECK_ALLOC(pkt->msg);
+
+ memcpy(pkt->msg, line, len);
+ pkt->msg[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+
+out_err:
+ giterr_set(GITERR_NET, "Invalid packet line");
+ git__free(pkt->ref);
+ git__free(pkt);
+ return -1;
+}
+
+static int unpack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_unpack *pkt;
+
+ GIT_UNUSED(len);
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_UNPACK;
+ if (!git__prefixcmp(line, "unpack ok"))
+ pkt->unpack_ok = 1;
+ else
+ pkt->unpack_ok = 0;
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int32_t parse_len(const char *line)
+{
+ char num[PKT_LEN_SIZE + 1];
+ int i, k, error;
+ int32_t len;
+ const char *num_end;
+
+ memcpy(num, line, PKT_LEN_SIZE);
+ num[PKT_LEN_SIZE] = '\0';
+
+ for (i = 0; i < PKT_LEN_SIZE; ++i) {
+ if (!isxdigit(num[i])) {
+ /* Make sure there are no special characters before passing to error message */
+ for (k = 0; k < PKT_LEN_SIZE; ++k) {
+ if(!isprint(num[k])) {
+ num[k] = '.';
+ }
+ }
+
+ giterr_set(GITERR_NET, "invalid hex digit in length: '%s'", num);
+ return -1;
+ }
+ }
+
+ if ((error = git__strtol32(&len, num, &num_end, 16)) < 0)
+ return error;
+
+ return len;
+}
+
+/*
+ * As per the documentation, the syntax is:
+ *
+ * pkt-line = data-pkt / flush-pkt
+ * data-pkt = pkt-len pkt-payload
+ * pkt-len = 4*(HEXDIG)
+ * pkt-payload = (pkt-len -4)*(OCTET)
+ * flush-pkt = "0000"
+ *
+ * Which means that the first four bytes are the length of the line,
+ * in ASCII hexadecimal (including itself)
+ */
+
+int git_pkt_parse_line(
+ git_pkt **head, const char *line, const char **out, size_t bufflen)
+{
+ int ret;
+ int32_t len;
+
+ /* Not even enough for the length */
+ if (bufflen > 0 && bufflen < PKT_LEN_SIZE)
+ return GIT_EBUFS;
+
+ len = parse_len(line);
+ if (len < 0) {
+ /*
+ * If we fail to parse the length, it might be because the
+ * server is trying to send us the packfile already.
+ */
+ if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) {
+ giterr_clear();
+ *out = line;
+ return pack_pkt(head);
+ }
+
+ return (int)len;
+ }
+
+ /*
+ * If we were given a buffer length, then make sure there is
+ * enough in the buffer to satisfy this line
+ */
+ if (bufflen > 0 && bufflen < (size_t)len)
+ return GIT_EBUFS;
+
+ line += PKT_LEN_SIZE;
+ /*
+ * TODO: How do we deal with empty lines? Try again? with the next
+ * line?
+ */
+ if (len == PKT_LEN_SIZE) {
+ *head = NULL;
+ *out = line;
+ return 0;
+ }
+
+ if (len == 0) { /* Flush pkt */
+ *out = line;
+ return flush_pkt(head);
+ }
+
+ len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
+
+ if (*line == GIT_SIDE_BAND_DATA)
+ ret = data_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_PROGRESS)
+ ret = sideband_progress_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_ERROR)
+ ret = sideband_error_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ACK"))
+ ret = ack_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "NAK"))
+ ret = nak_pkt(head);
+ else if (!git__prefixcmp(line, "ERR "))
+ ret = err_pkt(head, line, len);
+ else if (*line == '#')
+ ret = comment_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ok"))
+ ret = ok_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ng"))
+ ret = ng_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "unpack"))
+ ret = unpack_pkt(head, line, len);
+ else
+ ret = ref_pkt(head, line, len);
+
+ *out = line + len;
+
+ return ret;
+}
+
+void git_pkt_free(git_pkt *pkt)
+{
+ if (pkt->type == GIT_PKT_REF) {
+ git_pkt_ref *p = (git_pkt_ref *) pkt;
+ git__free(p->head.name);
+ git__free(p->head.symref_target);
+ }
+
+ if (pkt->type == GIT_PKT_OK) {
+ git_pkt_ok *p = (git_pkt_ok *) pkt;
+ git__free(p->ref);
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ git_pkt_ng *p = (git_pkt_ng *) pkt;
+ git__free(p->ref);
+ git__free(p->msg);
+ }
+
+ git__free(pkt);
+}
+
+int git_pkt_buffer_flush(git_buf *buf)
+{
+ return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
+}
+
+static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
+{
+ git_buf str = GIT_BUF_INIT;
+ char oid[GIT_OID_HEXSZ +1] = {0};
+ size_t len;
+
+ /* Prefer multi_ack_detailed */
+ if (caps->multi_ack_detailed)
+ git_buf_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " ");
+ else if (caps->multi_ack)
+ git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");
+
+ /* Prefer side-band-64k if the server supports both */
+ if (caps->side_band_64k)
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K);
+ else if (caps->side_band)
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND);
+
+ if (caps->include_tag)
+ git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");
+
+ if (caps->thin_pack)
+ git_buf_puts(&str, GIT_CAP_THIN_PACK " ");
+
+ if (caps->ofs_delta)
+ git_buf_puts(&str, GIT_CAP_OFS_DELTA " ");
+
+ if (git_buf_oom(&str))
+ return -1;
+
+ len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ +
+ git_buf_len(&str) + 1 /* LF */;
+
+ if (len > 0xffff) {
+ giterr_set(GITERR_NET,
+ "Tried to produce packet with invalid length %" PRIuZ, len);
+ return -1;
+ }
+
+ git_buf_grow_by(buf, len);
+ git_oid_fmt(oid, &head->oid);
+ git_buf_printf(buf,
+ "%04xwant %s %s\n", (unsigned int)len, oid, git_buf_cstr(&str));
+ git_buf_free(&str);
+
+ GITERR_CHECK_ALLOC_BUF(buf);
+
+ return 0;
+}
+
+/*
+ * All "want" packets have the same length and format, so what we do
+ * is overwrite the OID each time.
+ */
+
+int git_pkt_buffer_wants(
+ const git_remote_head * const *refs,
+ size_t count,
+ transport_smart_caps *caps,
+ git_buf *buf)
+{
+ size_t i = 0;
+ const git_remote_head *head;
+
+ if (caps->common) {
+ for (; i < count; ++i) {
+ head = refs[i];
+ if (!head->local)
+ break;
+ }
+
+ if (buffer_want_with_caps(refs[i], caps, buf) < 0)
+ return -1;
+
+ i++;
+ }
+
+ for (; i < count; ++i) {
+ char oid[GIT_OID_HEXSZ];
+
+ head = refs[i];
+ if (head->local)
+ continue;
+
+ git_oid_fmt(oid, &head->oid);
+ git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));
+ git_buf_put(buf, oid, GIT_OID_HEXSZ);
+ git_buf_putc(buf, '\n');
+ if (git_buf_oom(buf))
+ return -1;
+ }
+
+ return git_pkt_buffer_flush(buf);
+}
+
+int git_pkt_buffer_have(git_oid *oid, git_buf *buf)
+{
+ char oidhex[GIT_OID_HEXSZ + 1];
+
+ memset(oidhex, 0x0, sizeof(oidhex));
+ git_oid_fmt(oidhex, oid);
+ return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);
+}
+
+int git_pkt_buffer_done(git_buf *buf)
+{
+ return git_buf_puts(buf, pkt_done_str);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "git2/odb_backend.h"
+
+#include "smart.h"
+#include "refs.h"
+#include "repository.h"
+#include "push.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "util.h"
+
+#define NETWORK_XFER_THRESHOLD (100*1024)
+/* The minimal interval between progress updates (in seconds). */
+#define MIN_PROGRESS_UPDATE_INTERVAL 0.5
+
+int git_smart__store_refs(transport_smart *t, int flushes)
+{
+ gitno_buffer *buf = &t->buffer;
+ git_vector *refs = &t->refs;
+ int error, flush = 0, recvd;
+ const char *line_end = NULL;
+ git_pkt *pkt = NULL;
+ size_t i;
+
+ /* Clear existing refs in case git_remote_connect() is called again
+ * after git_remote_disconnect().
+ */
+ git_vector_foreach(refs, i, pkt) {
+ git_pkt_free(pkt);
+ }
+ git_vector_clear(refs);
+ pkt = NULL;
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return error;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return recvd;
+
+ if (recvd == 0) {
+ giterr_set(GITERR_NET, "early EOF");
+ return GIT_EEOF;
+ }
+
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+ if (pkt->type == GIT_PKT_ERR) {
+ giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
+ git__free(pkt);
+ return -1;
+ }
+
+ if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_FLUSH) {
+ flush++;
+ git_pkt_free(pkt);
+ }
+ } while (flush < flushes);
+
+ return flush;
+}
+
+static int append_symref(const char **out, git_vector *symrefs, const char *ptr)
+{
+ int error;
+ const char *end;
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec *mapping = NULL;
+
+ ptr += strlen(GIT_CAP_SYMREF);
+ if (*ptr != '=')
+ goto on_invalid;
+
+ ptr++;
+ if (!(end = strchr(ptr, ' ')) &&
+ !(end = strchr(ptr, '\0')))
+ goto on_invalid;
+
+ if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0)
+ return error;
+
+ /* symref mapping has refspec format */
+ mapping = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(mapping);
+
+ error = git_refspec__parse(mapping, git_buf_cstr(&buf), true);
+ git_buf_free(&buf);
+
+ /* if the error isn't OOM, then it's a parse error; let's use a nicer message */
+ if (error < 0) {
+ if (giterr_last()->klass != GITERR_NOMEMORY)
+ goto on_invalid;
+
+ git__free(mapping);
+ return error;
+ }
+
+ if ((error = git_vector_insert(symrefs, mapping)) < 0)
+ return error;
+
+ *out = end;
+ return 0;
+
+on_invalid:
+ giterr_set(GITERR_NET, "remote sent invalid symref");
+ git_refspec__free(mapping);
+ git__free(mapping);
+ return -1;
+}
+
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs)
+{
+ const char *ptr;
+
+ /* No refs or capabilites, odd but not a problem */
+ if (pkt == NULL || pkt->capabilities == NULL)
+ return 0;
+
+ ptr = pkt->capabilities;
+ while (ptr != NULL && *ptr != '\0') {
+ if (*ptr == ' ')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ caps->common = caps->ofs_delta = 1;
+ ptr += strlen(GIT_CAP_OFS_DELTA);
+ continue;
+ }
+
+ /* Keep multi_ack_detailed before multi_ack */
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) {
+ caps->common = caps->multi_ack_detailed = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
+ caps->common = caps->multi_ack = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ caps->common = caps->include_tag = 1;
+ ptr += strlen(GIT_CAP_INCLUDE_TAG);
+ continue;
+ }
+
+ /* Keep side-band check after side-band-64k */
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
+ caps->common = caps->side_band_64k = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND_64K);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
+ caps->common = caps->side_band = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {
+ caps->common = caps->delete_refs = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) {
+ caps->common = caps->thin_pack = 1;
+ ptr += strlen(GIT_CAP_THIN_PACK);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) {
+ int error;
+
+ if ((error = append_symref(&ptr, symrefs, ptr)) < 0)
+ return error;
+
+ continue;
+ }
+
+ /* We don't know this capability, so skip it */
+ ptr = strchr(ptr, ' ');
+ }
+
+ return 0;
+}
+
+static int recv_pkt(git_pkt **out, gitno_buffer *buf)
+{
+ const char *ptr = buf->data, *line_end = ptr;
+ git_pkt *pkt = NULL;
+ int pkt_type, error = 0, ret;
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error == 0)
+ break; /* return the pkt */
+
+ if (error < 0 && error != GIT_EBUFS)
+ return error;
+
+ if ((ret = gitno_recv(buf)) < 0) {
+ return ret;
+ } else if (ret == 0) {
+ giterr_set(GITERR_NET, "early EOF");
+ return GIT_EEOF;
+ }
+ } while (error);
+
+ gitno_consume(buf, line_end);
+ pkt_type = pkt->type;
+ if (out != NULL)
+ *out = pkt;
+ else
+ git__free(pkt);
+
+ return pkt_type;
+}
+
+static int store_common(transport_smart *t)
+{
+ git_pkt *pkt = NULL;
+ gitno_buffer *buf = &t->buffer;
+ int error;
+
+ do {
+ if ((error = recv_pkt(&pkt, buf)) < 0)
+ return error;
+
+ if (pkt->type == GIT_PKT_ACK) {
+ if (git_vector_insert(&t->common, pkt) < 0)
+ return -1;
+ } else {
+ git__free(pkt);
+ return 0;
+ }
+
+ } while (1);
+
+ return 0;
+}
+
+static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
+{
+ git_revwalk *walk = NULL;
+ git_strarray refs;
+ unsigned int i;
+ git_reference *ref;
+ int error;
+
+ if ((error = git_reference_list(&refs, repo)) < 0)
+ return error;
+
+ if ((error = git_revwalk_new(&walk, repo)) < 0)
+ return error;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ for (i = 0; i < refs.count; ++i) {
+ /* No tags */
+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
+ continue;
+
+ if ((error = git_reference_lookup(&ref, repo, refs.strings[i])) < 0)
+ goto on_error;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+
+ if ((error = git_revwalk_push(walk, git_reference_target(ref))) < 0)
+ goto on_error;
+
+ git_reference_free(ref);
+ }
+
+ git_strarray_free(&refs);
+ *out = walk;
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ git_reference_free(ref);
+ git_strarray_free(&refs);
+ return error;
+}
+
+static int wait_while_ack(gitno_buffer *buf)
+{
+ int error;
+ git_pkt_ack *pkt = NULL;
+
+ while (1) {
+ git__free(pkt);
+
+ if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)
+ return error;
+
+ if (pkt->type == GIT_PKT_NAK)
+ break;
+
+ if (pkt->type == GIT_PKT_ACK &&
+ (pkt->status != GIT_ACK_CONTINUE &&
+ pkt->status != GIT_ACK_COMMON)) {
+ git__free(pkt);
+ return 0;
+ }
+ }
+
+ git__free(pkt);
+ return 0;
+}
+
+int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_buf data = GIT_BUF_INIT;
+ git_revwalk *walk = NULL;
+ int error = -1, pkt_type;
+ unsigned int i;
+ git_oid oid;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ return error;
+
+ if ((error = fetch_setup_walk(&walk, repo)) < 0)
+ goto on_error;
+
+ /*
+ * Our support for ACK extensions is simply to parse them. On
+ * the first ACK we will accept that as enough common
+ * objects. We give up if we haven't found an answer in the
+ * first 256 we send.
+ */
+ i = 0;
+ while (i < 256) {
+ error = git_revwalk_next(&oid, walk);
+
+ if (error < 0) {
+ if (GIT_ITEROVER == error)
+ break;
+
+ goto on_error;
+ }
+
+ git_pkt_buffer_have(&oid, &data);
+ i++;
+ if (i % 20 == 0) {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ git_pkt_buffer_flush(&data);
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_clear(&data);
+ if (t->caps.multi_ack || t->caps.multi_ack_detailed) {
+ if ((error = store_common(t)) < 0)
+ goto on_error;
+ } else {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type == GIT_PKT_ACK) {
+ break;
+ } else if (pkt_type == GIT_PKT_NAK) {
+ continue;
+ } else if (pkt_type < 0) {
+ /* recv_pkt returned an error */
+ error = pkt_type;
+ goto on_error;
+ } else {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ if (t->common.length > 0)
+ break;
+
+ if (i % 20 == 0 && t->rpc) {
+ git_pkt_ack *pkt;
+ unsigned int j;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, j, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ /* Tell the other end that we're done negotiating */
+ if (t->rpc && t->common.length > 0) {
+ git_pkt_ack *pkt;
+ unsigned int j;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, j, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+ if ((error = git_pkt_buffer_done(&data)) < 0)
+ goto on_error;
+
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_free(&data);
+ git_revwalk_free(walk);
+
+ /* Now let's eat up whatever the server gives us */
+ if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type < 0) {
+ return pkt_type;
+ } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ return -1;
+ }
+ } else {
+ error = wait_while_ack(buf);
+ }
+
+ return error;
+
+on_error:
+ git_revwalk_free(walk);
+ git_buf_free(&data);
+ return error;
+}
+
+static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats)
+{
+ int recvd;
+
+ do {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ return GIT_EUSER;
+ }
+
+ if (writepack->append(writepack, buf->data, buf->offset, stats) < 0)
+ return -1;
+
+ gitno_consume_n(buf, buf->offset);
+
+ if ((recvd = gitno_recv(buf)) < 0)
+ return recvd;
+ } while(recvd > 0);
+
+ if (writepack->commit(writepack, stats) < 0)
+ return -1;
+
+ return 0;
+}
+
+struct network_packetsize_payload
+{
+ git_transfer_progress_cb callback;
+ void *payload;
+ git_transfer_progress *stats;
+ size_t last_fired_bytes;
+};
+
+static int network_packetsize(size_t received, void *payload)
+{
+ struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
+
+ /* Accumulate bytes */
+ npp->stats->received_bytes += received;
+
+ /* Fire notification if the threshold is reached */
+ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
+ npp->last_fired_bytes = npp->stats->received_bytes;
+
+ if (npp->callback(npp->stats, npp->payload))
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb transfer_progress_cb,
+ void *progress_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_odb *odb;
+ struct git_odb_writepack *writepack = NULL;
+ int error = 0;
+ struct network_packetsize_payload npp = {0};
+
+ memset(stats, 0, sizeof(git_transfer_progress));
+
+ if (transfer_progress_cb) {
+ npp.callback = transfer_progress_cb;
+ npp.payload = progress_payload;
+ npp.stats = stats;
+ t->packetsize_cb = &network_packetsize;
+ t->packetsize_payload = &npp;
+
+ /* We might have something in the buffer already from negotiate_fetch */
+ if (t->buffer.offset > 0 && !t->cancelled.val)
+ if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload))
+ git_atomic_set(&t->cancelled, 1);
+ }
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ ((error = git_odb_write_pack(&writepack, odb, transfer_progress_cb, progress_payload)) != 0))
+ goto done;
+
+ /*
+ * If the remote doesn't support the side-band, we can feed
+ * the data directly to the pack writer. Otherwise, we need to
+ * check which one belongs there.
+ */
+ if (!t->caps.side_band && !t->caps.side_band_64k) {
+ error = no_sideband(t, writepack, buf, stats);
+ goto done;
+ }
+
+ do {
+ git_pkt *pkt = NULL;
+
+ /* Check cancellation before network call */
+ if (t->cancelled.val) {
+ giterr_clear();
+ error = GIT_EUSER;
+ goto done;
+ }
+
+ if ((error = recv_pkt(&pkt, buf)) >= 0) {
+ /* Check cancellation after network call */
+ if (t->cancelled.val) {
+ giterr_clear();
+ error = GIT_EUSER;
+ } else if (pkt->type == GIT_PKT_PROGRESS) {
+ if (t->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ error = t->progress_cb(p->data, p->len, t->message_cb_payload);
+ }
+ } else if (pkt->type == GIT_PKT_DATA) {
+ git_pkt_data *p = (git_pkt_data *) pkt;
+
+ if (p->len)
+ error = writepack->append(writepack, p->data, p->len, stats);
+ } else if (pkt->type == GIT_PKT_FLUSH) {
+ /* A flush indicates the end of the packfile */
+ git__free(pkt);
+ break;
+ }
+ }
+
+ git__free(pkt);
+ if (error < 0)
+ goto done;
+
+ } while (1);
+
+ /*
+ * Trailing execution of transfer_progress_cb, if necessary...
+ * Only the callback through the npp datastructure currently
+ * updates the last_fired_bytes value. It is possible that
+ * progress has already been reported with the correct
+ * "received_bytes" value, but until (if?) this is unified
+ * then we will report progress again to be sure that the
+ * correct last received_bytes value is reported.
+ */
+ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) {
+ error = npp.callback(npp.stats, npp.payload);
+ if (error != 0)
+ goto done;
+ }
+
+ error = writepack->commit(writepack, stats);
+
+done:
+ if (writepack)
+ writepack->free(writepack);
+ if (transfer_progress_cb) {
+ t->packetsize_cb = NULL;
+ t->packetsize_payload = NULL;
+ }
+
+ return error;
+}
+
+static int gen_pktline(git_buf *buf, git_push *push)
+{
+ push_spec *spec;
+ size_t i, len;
+ char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1];
+
+ old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0';
+
+ git_vector_foreach(&push->specs, i, spec) {
+ len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->refspec.dst);
+
+ if (i == 0) {
+ ++len; /* '\0' */
+ if (push->report_status)
+ len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+ len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
+ }
+
+ git_oid_fmt(old_id, &spec->roid);
+ git_oid_fmt(new_id, &spec->loid);
+
+ git_buf_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst);
+
+ if (i == 0) {
+ git_buf_putc(buf, '\0');
+ /* Core git always starts their capabilities string with a space */
+ if (push->report_status) {
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);
+ }
+
+ git_buf_putc(buf, '\n');
+ }
+
+ git_buf_puts(buf, "0000");
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int add_push_report_pkt(git_push *push, git_pkt *pkt)
+{
+ push_status *status;
+
+ switch (pkt->type) {
+ case GIT_PKT_OK:
+ status = git__calloc(1, sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->msg = NULL;
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ if (!status->ref ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_NG:
+ status = git__calloc(1, sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ if (!status->ref || !status->msg ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_UNPACK:
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ break;
+ case GIT_PKT_FLUSH:
+ return GIT_ITEROVER;
+ default:
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_buf *data_pkt_buf)
+{
+ git_pkt *pkt;
+ const char *line, *line_end = NULL;
+ size_t line_len;
+ int error;
+ int reading_from_buf = data_pkt_buf->size > 0;
+
+ if (reading_from_buf) {
+ /* We had an existing partial packet, so add the new
+ * packet to the buffer and parse the whole thing */
+ git_buf_put(data_pkt_buf, data_pkt->data, data_pkt->len);
+ line = data_pkt_buf->ptr;
+ line_len = data_pkt_buf->size;
+ }
+ else {
+ line = data_pkt->data;
+ line_len = data_pkt->len;
+ }
+
+ while (line_len > 0) {
+ error = git_pkt_parse_line(&pkt, line, &line_end, line_len);
+
+ if (error == GIT_EBUFS) {
+ /* Buffer the data when the inner packet is split
+ * across multiple sideband packets */
+ if (!reading_from_buf)
+ git_buf_put(data_pkt_buf, line, line_len);
+ error = 0;
+ goto done;
+ }
+ else if (error < 0)
+ goto done;
+
+ /* Advance in the buffer */
+ line_len -= (line_end - line);
+ line = line_end;
+
+ /* When a valid packet with no content has been
+ * read, git_pkt_parse_line does not report an
+ * error, but the pkt pointer has not been set.
+ * Handle this by skipping over empty packets.
+ */
+ if (pkt == NULL)
+ continue;
+
+ error = add_push_report_pkt(push, pkt);
+
+ git_pkt_free(pkt);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+
+ error = 0;
+
+done:
+ if (reading_from_buf)
+ git_buf_consume(data_pkt_buf, line_end);
+ return error;
+}
+
+static int parse_report(transport_smart *transport, git_push *push)
+{
+ git_pkt *pkt = NULL;
+ const char *line_end = NULL;
+ gitno_buffer *buf = &transport->buffer;
+ int error, recvd;
+ git_buf data_pkt_buf = GIT_BUF_INIT;
+
+ for (;;) {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data,
+ &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS) {
+ error = -1;
+ goto done;
+ }
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0) {
+ error = recvd;
+ goto done;
+ }
+
+ if (recvd == 0) {
+ giterr_set(GITERR_NET, "early EOF");
+ error = GIT_EEOF;
+ goto done;
+ }
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+
+ error = 0;
+
+ if (pkt == NULL)
+ continue;
+
+ switch (pkt->type) {
+ case GIT_PKT_DATA:
+ /* This is a sideband packet which contains other packets */
+ error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf);
+ break;
+ case GIT_PKT_ERR:
+ giterr_set(GITERR_NET, "report-status: Error reported: %s",
+ ((git_pkt_err *)pkt)->error);
+ error = -1;
+ break;
+ case GIT_PKT_PROGRESS:
+ if (transport->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ error = transport->progress_cb(p->data, p->len, transport->message_cb_payload);
+ }
+ break;
+ default:
+ error = add_push_report_pkt(push, pkt);
+ break;
+ }
+
+ git_pkt_free(pkt);
+
+ /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */
+ if (error == GIT_ITEROVER) {
+ error = 0;
+ if (data_pkt_buf.size > 0) {
+ /* If there was data remaining in the pack data buffer,
+ * then the server sent a partial pkt-line */
+ giterr_set(GITERR_NET, "Incomplete pack data pkt-line");
+ error = GIT_ERROR;
+ }
+ goto done;
+ }
+
+ if (error < 0) {
+ goto done;
+ }
+ }
+done:
+ git_buf_free(&data_pkt_buf);
+ return error;
+}
+
+static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec)
+{
+ git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref));
+ GITERR_CHECK_ALLOC(added);
+
+ added->type = GIT_PKT_REF;
+ git_oid_cpy(&added->head.oid, &push_spec->loid);
+ added->head.name = git__strdup(push_spec->refspec.dst);
+
+ if (!added->head.name ||
+ git_vector_insert(refs, added) < 0) {
+ git_pkt_free((git_pkt *)added);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int update_refs_from_report(
+ git_vector *refs,
+ git_vector *push_specs,
+ git_vector *push_report)
+{
+ git_pkt_ref *ref;
+ push_spec *push_spec;
+ push_status *push_status;
+ size_t i, j, refs_len;
+ int cmp;
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report */
+ if (push_specs->length != push_report->length) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ /* We require that push_specs be sorted with push_spec_rref_cmp,
+ * and that push_report be sorted with push_status_ref_cmp */
+ git_vector_sort(push_specs);
+ git_vector_sort(push_report);
+
+ git_vector_foreach(push_specs, i, push_spec) {
+ push_status = git_vector_get(push_report, i);
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report which matches */
+ if (strcmp(push_spec->refspec.dst, push_status->ref)) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+ }
+
+ /* We require that refs be sorted with ref_name_cmp */
+ git_vector_sort(refs);
+ i = j = 0;
+ refs_len = refs->length;
+
+ /* Merge join push_specs with refs */
+ while (i < push_specs->length && j < refs_len) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+ ref = git_vector_get(refs, j);
+
+ cmp = strcmp(push_spec->refspec.dst, ref->head.name);
+
+ /* Iterate appropriately */
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+
+ /* Add case */
+ if (cmp < 0 &&
+ !push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+
+ /* Update case, delete case */
+ if (cmp == 0 &&
+ !push_status->msg)
+ git_oid_cpy(&ref->head.oid, &push_spec->loid);
+ }
+
+ for (; i < push_specs->length; i++) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+
+ /* Add case */
+ if (!push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+ }
+
+ /* Remove any refs which we updated to have a zero OID. */
+ git_vector_rforeach(refs, i, ref) {
+ if (git_oid_iszero(&ref->head.oid)) {
+ git_vector_remove(refs, i);
+ git_pkt_free((git_pkt *)ref);
+ }
+ }
+
+ git_vector_sort(refs);
+
+ return 0;
+}
+
+struct push_packbuilder_payload
+{
+ git_smart_subtransport_stream *stream;
+ git_packbuilder *pb;
+ git_push_transfer_progress cb;
+ void *cb_payload;
+ size_t last_bytes;
+ double last_progress_report_time;
+};
+
+static int stream_thunk(void *buf, size_t size, void *data)
+{
+ int error = 0;
+ struct push_packbuilder_payload *payload = data;
+
+ if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0)
+ return error;
+
+ if (payload->cb) {
+ double current_time = git__timer();
+ payload->last_bytes += size;
+
+ if ((current_time - payload->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ payload->last_progress_report_time = current_time;
+ error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload);
+ }
+ }
+
+ return error;
+}
+
+int git_smart__push(git_transport *transport, git_push *push, const git_remote_callbacks *cbs)
+{
+ transport_smart *t = (transport_smart *)transport;
+ struct push_packbuilder_payload packbuilder_payload = {0};
+ git_buf pktline = GIT_BUF_INIT;
+ int error = 0, need_pack = 0;
+ push_spec *spec;
+ unsigned int i;
+
+ packbuilder_payload.pb = push->pb;
+
+ if (cbs && cbs->push_transfer_progress) {
+ packbuilder_payload.cb = cbs->push_transfer_progress;
+ packbuilder_payload.cb_payload = cbs->payload;
+ }
+
+#ifdef PUSH_DEBUG
+{
+ git_remote_head *head;
+ char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0';
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ git_oid_fmt(hex, &head->oid);
+ fprintf(stderr, "%s (%s)\n", hex, head->name);
+ }
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_oid_fmt(hex, &spec->roid);
+ fprintf(stderr, "%s (%s) -> ", hex, spec->lref);
+ git_oid_fmt(hex, &spec->loid);
+ fprintf(stderr, "%s (%s)\n", hex, spec->rref ?
+ spec->rref : spec->lref);
+ }
+}
+#endif
+
+ /*
+ * Figure out if we need to send a packfile; which is in all
+ * cases except when we only send delete commands
+ */
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->refspec.src && spec->refspec.src[0] != '\0') {
+ need_pack = 1;
+ break;
+ }
+ }
+
+ if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 ||
+ (error = gen_pktline(&pktline, push)) < 0 ||
+ (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline))) < 0)
+ goto done;
+
+ if (need_pack &&
+ (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0)
+ goto done;
+
+ /* If we sent nothing or the server doesn't support report-status, then
+ * we consider the pack to have been unpacked successfully */
+ if (!push->specs.length || !push->report_status)
+ push->unpack_ok = 1;
+ else if ((error = parse_report(t, push)) < 0)
+ goto done;
+
+ /* If progress is being reported write the final report */
+ if (cbs && cbs->push_transfer_progress) {
+ error = cbs->push_transfer_progress(
+ push->pb->nr_written,
+ push->pb->nr_objects,
+ packbuilder_payload.last_bytes,
+ cbs->payload);
+
+ if (error < 0)
+ goto done;
+ }
+
+ if (push->status.length) {
+ error = update_refs_from_report(&t->refs, &push->specs, &push->status);
+ if (error < 0)
+ goto done;
+
+ error = git_smart__update_heads(t, NULL);
+ }
+
+done:
+ git_buf_free(&pktline);
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_SSH
+#include <libssh2.h>
+#endif
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+#include "smart.h"
+#include "cred.h"
+#include "socket_stream.h"
+#include "ssh.h"
+
+#ifdef GIT_SSH
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char *ssh_prefixes[] = { "ssh://", "ssh+git://", "git+ssh://" };
+
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ git_stream *io;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ ssh_stream *current_stream;
+ git_cred *cred;
+ char *cmd_uploadpack;
+ char *cmd_receivepack;
+} ssh_subtransport;
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
+
+static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
+{
+ char *ssherr;
+ libssh2_session_last_error(session, &ssherr, NULL, 0);
+
+ giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr);
+}
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: git-upload-pack '/libgit2/libgit2'
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *repo;
+ int len;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
+ const char *p = ssh_prefixes[i];
+
+ if (!git__prefixcmp(url, p)) {
+ url = url + strlen(p);
+ repo = strchr(url, '/');
+ if (repo && repo[1] == '~')
+ ++repo;
+
+ goto done;
+ }
+ }
+ repo = strchr(url, ':');
+ if (repo) repo++;
+
+done:
+ if (!repo) {
+ giterr_set(GITERR_NET, "Malformed git protocol URL");
+ return -1;
+ }
+
+ len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%s '%s'", cmd, repo);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = libssh2_channel_exec(s->channel, request.ptr);
+ if (error < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not execute request");
+ goto cleanup;
+ }
+
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int rc;
+ ssh_stream *s = (ssh_stream *)stream;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not read data");
+ return -1;
+ }
+
+ /*
+ * If we can't get anything out of stdout, it's typically a
+ * not-found error, so read from stderr and signal EOF on
+ * stderr.
+ */
+ if (rc == 0) {
+ if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) {
+ giterr_set(GITERR_SSH, "%*s", rc, buffer);
+ return GIT_EEOF;
+ } else if (rc < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not read stderr");
+ return -1;
+ }
+ }
+
+
+ *bytes_read = rc;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ size_t off = 0;
+ ssize_t ret = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ do {
+ ret = libssh2_channel_write(s->channel, buffer + off, len - off);
+ if (ret < 0)
+ break;
+
+ off += ret;
+
+ } while (off < len);
+
+ if (ret < 0) {
+ ssh_error(s->session, "SSH could not write data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t;
+
+ if (!stream)
+ return;
+
+ t = OWNING_SUBTRANSPORT(s);
+ t->current_stream = NULL;
+
+ if (s->channel) {
+ libssh2_channel_close(s->channel);
+ libssh2_channel_free(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->session) {
+ libssh2_session_free(s->session);
+ s->session = NULL;
+ }
+
+ if (s->io) {
+ git_stream_close(s->io);
+ git_stream_free(s->io);
+ s->io = NULL;
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ ssh_stream *s;
+
+ assert(stream);
+
+ s = git__calloc(sizeof(ssh_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = ssh_stream_read;
+ s->parent.write = ssh_stream_write;
+ s->parent.free = ssh_stream_free;
+
+ s->cmd = cmd;
+
+ s->url = git__strdup(url);
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int git_ssh_extract_url_parts(
+ char **host,
+ char **username,
+ const char *url)
+{
+ char *colon, *at;
+ const char *start;
+
+ colon = strchr(url, ':');
+
+
+ at = strchr(url, '@');
+ if (at) {
+ start = at + 1;
+ *username = git__substrdup(url, at - url);
+ GITERR_CHECK_ALLOC(*username);
+ } else {
+ start = url;
+ *username = NULL;
+ }
+
+ if (colon == NULL || (colon < start)) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ *host = git__substrdup(start, colon - start);
+ GITERR_CHECK_ALLOC(*host);
+
+ return 0;
+}
+
+static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) {
+ int rc = LIBSSH2_ERROR_NONE;
+
+ struct libssh2_agent_publickey *curr, *prev = NULL;
+
+ LIBSSH2_AGENT *agent = libssh2_agent_init(session);
+
+ if (agent == NULL)
+ return -1;
+
+ rc = libssh2_agent_connect(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ goto shutdown;
+
+ rc = libssh2_agent_list_identities(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ goto shutdown;
+
+ while (1) {
+ rc = libssh2_agent_get_identity(agent, &curr, prev);
+
+ if (rc < 0)
+ goto shutdown;
+
+ /* rc is set to 1 whenever the ssh agent ran out of keys to check.
+ * Set the error code to authentication failure rather than erroring
+ * out with an untranslatable error code.
+ */
+ if (rc == 1) {
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ goto shutdown;
+ }
+
+ rc = libssh2_agent_userauth(agent, c->username, curr);
+
+ if (rc == 0)
+ break;
+
+ prev = curr;
+ }
+
+shutdown:
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ ssh_error(session, "error authenticating");
+
+ libssh2_agent_disconnect(agent);
+ libssh2_agent_free(agent);
+
+ return rc;
+}
+
+static int _git_ssh_authenticate_session(
+ LIBSSH2_SESSION* session,
+ git_cred* cred)
+{
+ int rc;
+
+ do {
+ giterr_clear();
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ rc = libssh2_userauth_password(session, c->username, c->password);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_KEY: {
+ git_cred_ssh_key *c = (git_cred_ssh_key *)cred;
+
+ if (c->privatekey)
+ rc = libssh2_userauth_publickey_fromfile(
+ session, c->username, c->publickey,
+ c->privatekey, c->passphrase);
+ else
+ rc = ssh_agent_auth(session, c);
+
+ break;
+ }
+ case GIT_CREDTYPE_SSH_CUSTOM: {
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
+
+ rc = libssh2_userauth_publickey(
+ session, c->username, (const unsigned char *)c->publickey,
+ c->publickey_len, c->sign_callback, &c->payload);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_INTERACTIVE: {
+ void **abstract = libssh2_session_abstract(session);
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
+
+ /* ideally, we should be able to set this by calling
+ * libssh2_session_init_ex() instead of libssh2_session_init().
+ * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
+ * allows you to pass the `abstract` as part of the call, whereas
+ * libssh2_userauth_keyboard_interactive() does not!
+ *
+ * The only way to set the `abstract` pointer is by calling
+ * libssh2_session_abstract(), which will replace the existing
+ * pointer as is done below. This is safe for now (at time of writing),
+ * but may not be valid in future.
+ */
+ *abstract = c->payload;
+
+ rc = libssh2_userauth_keyboard_interactive(
+ session, c->username, c->prompt_callback);
+ break;
+ }
+#ifdef GIT_SSH_MEMORY_CREDENTIALS
+ case GIT_CREDTYPE_SSH_MEMORY: {
+ git_cred_ssh_key *c = (git_cred_ssh_key *)cred;
+
+ assert(c->username);
+ assert(c->privatekey);
+
+ rc = libssh2_userauth_publickey_frommemory(
+ session,
+ c->username,
+ strlen(c->username),
+ c->publickey,
+ c->publickey ? strlen(c->publickey) : 0,
+ c->privatekey,
+ strlen(c->privatekey),
+ c->passphrase);
+ break;
+ }
+#endif
+ default:
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ }
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
+ return GIT_EAUTH;
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ if (!giterr_last())
+ ssh_error(session, "Failed to authenticate SSH session");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods)
+{
+ int error, no_callback = 0;
+ git_cred *cred = NULL;
+
+ if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH)
+ no_callback = 1;
+ else if (error < 0)
+ return error;
+ else if (!cred) {
+ giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
+ return -1;
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_SSH, "authentication required but no callback set");
+ return -1;
+ }
+
+ if (!(cred->credtype & auth_methods)) {
+ cred->free(cred);
+ giterr_set(GITERR_SSH, "callback returned unsupported credentials type");
+ return -1;
+ }
+
+ *out = cred;
+
+ return 0;
+}
+
+static int _git_ssh_session_create(
+ LIBSSH2_SESSION** session,
+ git_stream *io)
+{
+ int rc = 0;
+ LIBSSH2_SESSION* s;
+ git_socket_stream *socket = (git_socket_stream *) io;
+
+ assert(session);
+
+ s = libssh2_session_init();
+ if (!s) {
+ giterr_set(GITERR_NET, "Failed to initialize SSH session");
+ return -1;
+ }
+
+ do {
+ rc = libssh2_session_startup(s, socket->s);
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ ssh_error(s, "Failed to start SSH session");
+ libssh2_session_free(s);
+ return -1;
+ }
+
+ libssh2_session_set_blocking(s, 1);
+
+ *session = s;
+
+ return 0;
+}
+
+static int _git_ssh_setup_conn(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *default_port="22";
+ int auth_methods, error = 0;
+ size_t i;
+ ssh_stream *s;
+ git_cred *cred = NULL;
+ LIBSSH2_SESSION* session=NULL;
+ LIBSSH2_CHANNEL* channel=NULL;
+
+ t->current_stream = NULL;
+
+ *stream = NULL;
+ if (ssh_stream_alloc(t, url, cmd, stream) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+ s->session = NULL;
+ s->channel = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
+ const char *p = ssh_prefixes[i];
+
+ if (!git__prefixcmp(url, p)) {
+ if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
+ goto done;
+
+ goto post_extract;
+ }
+ }
+ if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
+ goto done;
+ port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(port);
+
+post_extract:
+ if ((error = git_socket_stream_new(&s->io, host, port)) < 0 ||
+ (error = git_stream_connect(s->io)) < 0)
+ goto done;
+
+ if ((error = _git_ssh_session_create(&session, s->io)) < 0)
+ goto done;
+
+ if (t->owner->certificate_check_cb != NULL) {
+ git_cert_hostkey cert = {{ 0 }}, *cert_ptr;
+ const char *key;
+
+ cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_SHA1;
+ memcpy(&cert.hash_sha1, key, 20);
+ }
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_MD5;
+ memcpy(&cert.hash_md5, key, 16);
+ }
+
+ if (cert.type == 0) {
+ giterr_set(GITERR_SSH, "unable to get the host key");
+ error = -1;
+ goto done;
+ }
+
+ /* We don't currently trust any hostkeys */
+ giterr_clear();
+
+ cert_ptr = &cert;
+
+ error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload);
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "user cancelled hostkey check");
+
+ goto done;
+ }
+ }
+
+ /* we need the username to ask for auth methods */
+ if (!user) {
+ if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
+ goto done;
+
+ user = git__strdup(((git_cred_username *) cred)->username);
+ cred->free(cred);
+ cred = NULL;
+ if (!user)
+ goto done;
+ } else if (user && pass) {
+ if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0)
+ goto done;
+ }
+
+ if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
+ goto done;
+
+ error = GIT_EAUTH;
+ /* if we already have something to try */
+ if (cred && auth_methods & cred->credtype)
+ error = _git_ssh_authenticate_session(session, cred);
+
+ while (error == GIT_EAUTH) {
+ if (cred) {
+ cred->free(cred);
+ cred = NULL;
+ }
+
+ if ((error = request_creds(&cred, t, user, auth_methods)) < 0)
+ goto done;
+
+ if (strcmp(user, git_cred__username(cred))) {
+ giterr_set(GITERR_SSH, "username does not match previous request");
+ error = -1;
+ goto done;
+ }
+
+ error = _git_ssh_authenticate_session(session, cred);
+ }
+
+ if (error < 0)
+ goto done;
+
+ channel = libssh2_channel_open_session(session);
+ if (!channel) {
+ error = -1;
+ ssh_error(session, "Failed to open SSH channel");
+ goto done;
+ }
+
+ libssh2_channel_set_blocking(channel, 1);
+
+ s->session = session;
+ s->channel = channel;
+
+ t->current_stream = s;
+
+done:
+ if (error < 0) {
+ ssh_stream_free(*stream);
+
+ if (session)
+ libssh2_session_free(session);
+ }
+
+ if (cred)
+ cred->free(cred);
+
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+
+ return error;
+}
+
+static int ssh_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
+}
+
+static int ssh_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int ssh_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
+
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
+}
+
+static int ssh_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _ssh_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return ssh_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return ssh_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _ssh_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _ssh_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
+ git__free(t);
+}
+
+#define SSH_AUTH_PUBLICKEY "publickey"
+#define SSH_AUTH_PASSWORD "password"
+#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
+{
+ const char *list, *ptr;
+
+ *out = 0;
+
+ list = libssh2_userauth_list(session, username, strlen(username));
+
+ /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
+ if (list == NULL && !libssh2_userauth_authenticated(session)) {
+ ssh_error(session, "Failed to retrieve list of SSH authentication methods");
+ return -1;
+ }
+
+ ptr = list;
+ while (ptr) {
+ if (*ptr == ',')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
+ *out |= GIT_CREDTYPE_SSH_KEY;
+ *out |= GIT_CREDTYPE_SSH_CUSTOM;
+#ifdef GIT_SSH_MEMORY_CREDENTIALS
+ *out |= GIT_CREDTYPE_SSH_MEMORY;
+#endif
+ ptr += strlen(SSH_AUTH_PUBLICKEY);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
+ *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ ptr += strlen(SSH_AUTH_PASSWORD);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
+ *out |= GIT_CREDTYPE_SSH_INTERACTIVE;
+ ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
+ continue;
+ }
+
+ /* Skipt it if we don't know it */
+ ptr = strchr(ptr, ',');
+ }
+
+ return 0;
+}
+#endif
+
+int git_smart_subtransport_ssh(
+ git_smart_subtransport **out, git_transport *owner, void *param)
+{
+#ifdef GIT_SSH
+ ssh_subtransport *t;
+
+ assert(out);
+
+ GIT_UNUSED(param);
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = _ssh_action;
+ t->parent.close = _ssh_close;
+ t->parent.free = _ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+ GIT_UNUSED(param);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
+
+int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
+{
+#ifdef GIT_SSH
+ git_strarray *paths = (git_strarray *) payload;
+ git_transport *transport;
+ transport_smart *smart;
+ ssh_subtransport *t;
+ int error;
+ git_smart_subtransport_definition ssh_definition = {
+ git_smart_subtransport_ssh,
+ 0, /* no RPC */
+ NULL,
+ };
+
+ if (paths->count != 2) {
+ giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
+ return error;
+
+ smart = (transport_smart *) transport;
+ t = (ssh_subtransport *) smart->wrapped;
+
+ t->cmd_uploadpack = git__strdup(paths->strings[0]);
+ GITERR_CHECK_ALLOC(t->cmd_uploadpack);
+ t->cmd_receivepack = git__strdup(paths->strings[1]);
+ GITERR_CHECK_ALLOC(t->cmd_receivepack);
+
+ *out = transport;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+ GIT_UNUSED(payload);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
+
+int git_transport_ssh_global_init(void)
+{
+#ifdef GIT_SSH
+
+ libssh2_init(0);
+ return 0;
+
+#else
+
+ /* Nothing to initialize */
+ return 0;
+
+#endif
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_ssh_h__
+#define INCLUDE_ssh_h__
+
+int git_transport_ssh_global_init(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_WINHTTP
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "posix.h"
+#include "netops.h"
+#include "smart.h"
+#include "remote.h"
+#include "repository.h"
+#include "global.h"
+
+#include <wincrypt.h>
+#include <winhttp.h>
+
+/* For IInternetSecurityManager zone check */
+#include <objbase.h>
+#include <urlmon.h>
+
+#define WIDEN2(s) L ## s
+#define WIDEN(s) WIDEN2(s)
+
+#define MAX_CONTENT_TYPE_LEN 100
+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+#define CACHED_POST_BODY_BUF_SIZE 4096
+#define UUID_LENGTH_CCH 32
+#define TIMEOUT_INFINITE -1
+#define DEFAULT_CONNECT_TIMEOUT 60000
+#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH
+#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0
+#endif
+
+static const char *prefix_https = "https://";
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const wchar_t *get_verb = L"GET";
+static const wchar_t *post_verb = L"POST";
+static const wchar_t *pragma_nocache = L"Pragma: no-cache";
+static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
+static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+#if defined(__MINGW32__)
+static const CLSID CLSID_InternetSecurityManager_mingw =
+ { 0x7B8A2D94, 0x0AC9, 0x11D1,
+ { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } };
+static const IID IID_IInternetSecurityManager_mingw =
+ { 0x79EAC9EE, 0xBAF9, 0x11CE,
+ { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } };
+
+# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw
+# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw
+#endif
+
+#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
+
+typedef enum {
+ GIT_WINHTTP_AUTH_BASIC = 1,
+ GIT_WINHTTP_AUTH_NEGOTIATE = 2,
+} winhttp_authmechanism_t;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const wchar_t *verb;
+ HINTERNET request;
+ wchar_t *request_uri;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ HANDLE post_body;
+ DWORD post_body_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1;
+} winhttp_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_connection_data connection_data;
+ gitno_connection_data proxy_connection_data;
+ git_cred *cred;
+ git_cred *url_cred;
+ git_cred *proxy_cred;
+ int auth_mechanism;
+ HINTERNET session;
+ HINTERNET connection;
+} winhttp_subtransport;
+
+static int apply_basic_credential_proxy(HINTERNET request, git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ wchar_t *user, *pass;
+ int error;
+
+ if ((error = git__utf8_to_16_alloc(&user, c->username)) < 0)
+ return error;
+
+ if ((error = git__utf8_to_16_alloc(&pass, c->password)) < 0)
+ return error;
+
+ if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_PROXY, WINHTTP_AUTH_SCHEME_BASIC,
+ user, pass, NULL)) {
+ giterr_set(GITERR_OS, "failed to set proxy auth");
+ error = -1;
+ }
+
+ git__free(user);
+ git__free(pass);
+
+ return error;
+}
+
+static int apply_basic_credential(HINTERNET request, git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;
+ wchar_t *wide = NULL;
+ int error = -1, wide_len;
+
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(&buf, "Authorization: Basic ") < 0 ||
+ git_buf_encode_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
+ goto on_error;
+
+ if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */
+ if (wide)
+ memset(wide, 0x0, wide_len * sizeof(wchar_t));
+
+ if (buf.size)
+ memset(buf.ptr, 0x0, buf.size);
+
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git__free(wide);
+ git_buf_free(&buf);
+ git_buf_free(&raw);
+ return error;
+}
+
+static int apply_default_credentials(HINTERNET request)
+{
+ /* Either the caller explicitly requested that default credentials be passed,
+ * or our fallback credential callback was invoked and checked that the target
+ * URI was in the appropriate Internet Explorer security zone. By setting this
+ * flag, we guarantee that the credentials are delivered by WinHTTP. The default
+ * is "medium" which applies to the intranet and sounds like it would correspond
+ * to Internet Explorer security zones, but in fact does not. */
+ DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
+
+ if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD)))
+ return -1;
+
+ return 0;
+}
+
+static int fallback_cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ int error = 1;
+
+ GIT_UNUSED(username_from_url);
+ GIT_UNUSED(payload);
+
+ /* If the target URI supports integrated Windows authentication
+ * as an authentication mechanism */
+ if (GIT_CREDTYPE_DEFAULT & allowed_types) {
+ wchar_t *wide_url;
+
+ /* Convert URL to wide characters */
+ if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ return -1;
+ }
+
+ if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
+ IInternetSecurityManager* pISM;
+
+ /* And if the target URI is in the My Computer, Intranet, or Trusted zones */
+ if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL,
+ CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) {
+ DWORD dwZone;
+
+ if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) &&
+ (URLZONE_LOCAL_MACHINE == dwZone ||
+ URLZONE_INTRANET == dwZone ||
+ URLZONE_TRUSTED == dwZone)) {
+ git_cred *existing = *cred;
+
+ if (existing)
+ existing->free(existing);
+
+ /* Then use default Windows credentials to authenticate this request */
+ error = git_cred_default_new(cred);
+ }
+
+ pISM->lpVtbl->Release(pISM);
+ }
+
+ CoUninitialize();
+ }
+
+ git__free(wide_url);
+ }
+
+ return error;
+}
+
+static int certificate_check(winhttp_stream *s, int valid)
+{
+ int error;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ PCERT_CONTEXT cert_ctx;
+ DWORD cert_ctx_size = sizeof(cert_ctx);
+ git_cert_x509 cert;
+
+ /* If there is no override, we should fail if WinHTTP doesn't think it's fine */
+ if (t->owner->certificate_check_cb == NULL && !valid)
+ return GIT_ECERTIFICATE;
+
+ if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
+ return 0;
+
+ if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
+ giterr_set(GITERR_OS, "failed to get server certificate");
+ return -1;
+ }
+
+ giterr_clear();
+ cert.parent.cert_type = GIT_CERT_X509;
+ cert.data = cert_ctx->pbCertEncoded;
+ cert.len = cert_ctx->cbCertEncoded;
+ error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->cred_acquire_payload);
+ CertFreeCertificateContext(cert_ctx);
+
+ if (error < 0 && !giterr_last())
+ giterr_set(GITERR_NET, "user cancelled certificate check");
+
+ return error;
+}
+
+static void winhttp_stream_close(winhttp_stream *s)
+{
+ if (s->chunk_buffer) {
+ git__free(s->chunk_buffer);
+ s->chunk_buffer = NULL;
+ }
+
+ if (s->post_body) {
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (s->request_uri) {
+ git__free(s->request_uri);
+ s->request_uri = NULL;
+ }
+
+ if (s->request) {
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ }
+
+ s->sent_request = 0;
+}
+
+/**
+ * Extract the url and password from a URL. The outputs are pointers
+ * into the input.
+ */
+static int userpass_from_url(wchar_t **user, int *user_len,
+ wchar_t **pass, int *pass_len,
+ const wchar_t *url, int url_len)
+{
+ URL_COMPONENTS components = { 0 };
+
+ components.dwStructSize = sizeof(components);
+ /* These tell WinHttpCrackUrl that we're interested in the fields */
+ components.dwUserNameLength = 1;
+ components.dwPasswordLength = 1;
+
+ if (!WinHttpCrackUrl(url, url_len, 0, &components)) {
+ giterr_set(GITERR_OS, "failed to extract user/pass from url");
+ return -1;
+ }
+
+ *user = components.lpszUserName;
+ *user_len = components.dwUserNameLength;
+ *pass = components.lpszPassword;
+ *pass_len = components.dwPasswordLength;
+
+ return 0;
+}
+
+#define SCHEME_HTTP "http://"
+#define SCHEME_HTTPS "https://"
+
+static int winhttp_stream_connect(winhttp_stream *s)
+{
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf buf = GIT_BUF_INIT;
+ char *proxy_url = NULL;
+ wchar_t ct[MAX_CONTENT_TYPE_LEN];
+ LPCWSTR types[] = { L"*/*", NULL };
+ BOOL peerdist = FALSE;
+ int error = -1;
+ unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
+ int default_timeout = TIMEOUT_INFINITE;
+ int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+ size_t i;
+ const git_proxy_options *proxy_opts;
+
+ /* Prepare URL */
+ git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* Convert URL to wide characters */
+ if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ /* Establish request */
+ s->request = WinHttpOpenRequest(
+ t->connection,
+ s->verb,
+ s->request_uri,
+ NULL,
+ WINHTTP_NO_REFERER,
+ types,
+ t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0);
+
+ if (!s->request) {
+ giterr_set(GITERR_OS, "Failed to open request");
+ goto on_error;
+ }
+
+ if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
+ giterr_set(GITERR_OS, "Failed to set timeouts for WinHTTP");
+ goto on_error;
+ }
+
+ proxy_opts = &t->owner->proxy;
+ if (proxy_opts->type == GIT_PROXY_AUTO) {
+ /* Set proxy if necessary */
+ if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
+ goto on_error;
+ }
+ else if (proxy_opts->type == GIT_PROXY_SPECIFIED) {
+ proxy_url = git__strdup(proxy_opts->url);
+ GITERR_CHECK_ALLOC(proxy_url);
+ }
+
+ if (proxy_url) {
+ git_buf processed_url = GIT_BUF_INIT;
+ WINHTTP_PROXY_INFO proxy_info;
+ wchar_t *proxy_wide;
+
+ if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) {
+ t->proxy_connection_data.use_ssl = false;
+ } else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) {
+ t->proxy_connection_data.use_ssl = true;
+ } else {
+ giterr_set(GITERR_NET, "invalid URL: '%s'", proxy_url);
+ return -1;
+ }
+
+ gitno_connection_data_free_ptrs(&t->proxy_connection_data);
+
+ if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL,
+ &t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0)
+ goto on_error;
+
+ if (t->proxy_connection_data.user && t->proxy_connection_data.pass) {
+ if (t->proxy_cred) {
+ t->proxy_cred->free(t->proxy_cred);
+ }
+
+ if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0)
+ goto on_error;
+ }
+
+ if (t->proxy_connection_data.use_ssl)
+ git_buf_PUTS(&processed_url, SCHEME_HTTPS);
+ else
+ git_buf_PUTS(&processed_url, SCHEME_HTTP);
+
+ git_buf_puts(&processed_url, t->proxy_connection_data.host);
+ if (t->proxy_connection_data.port)
+ git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port);
+
+ if (git_buf_oom(&processed_url)) {
+ giterr_set_oom();
+ error = -1;
+ goto on_error;
+ }
+
+ /* Convert URL to wide characters */
+ error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
+ git_buf_free(&processed_url);
+ if (error < 0)
+ goto on_error;
+
+ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy_info.lpszProxy = proxy_wide;
+ proxy_info.lpszProxyBypass = NULL;
+
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PROXY,
+ &proxy_info,
+ sizeof(WINHTTP_PROXY_INFO))) {
+ giterr_set(GITERR_OS, "Failed to set proxy");
+ git__free(proxy_wide);
+ goto on_error;
+ }
+
+ git__free(proxy_wide);
+
+ if (t->proxy_cred) {
+ if (t->proxy_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT) {
+ if ((error = apply_basic_credential_proxy(s->request, t->proxy_cred)) < 0)
+ goto on_error;
+ }
+ }
+
+ }
+
+ /* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
+ * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
+ */
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_DISABLE_FEATURE,
+ &disable_redirects,
+ sizeof(disable_redirects))) {
+ giterr_set(GITERR_OS, "Failed to disable redirects");
+ goto on_error;
+ }
+
+ /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
+ * adds itself. This option may not be supported by the underlying
+ * platform, so we do not error-check it */
+ WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PEERDIST_EXTENSION_STATE,
+ &peerdist,
+ sizeof(peerdist));
+
+ /* Send Pragma: no-cache header */
+ if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ if (post_verb == s->verb) {
+ /* Send Content-Type and Accept headers -- only necessary on a POST */
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf,
+ "Content-Type: application/x-git-%s-request",
+ s->service) < 0)
+ goto on_error;
+
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert content-type to wide characters");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
+ WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf,
+ "Accept: application/x-git-%s-result",
+ s->service) < 0)
+ goto on_error;
+
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert accept header to wide characters");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
+ WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+ }
+
+ for (i = 0; i < t->owner->custom_headers.count; i++) {
+ if (t->owner->custom_headers.strings[i]) {
+ git_buf_clear(&buf);
+ git_buf_puts(&buf, t->owner->custom_headers.strings[i]);
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert custom header to wide characters");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
+ WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+ }
+ }
+
+ /* If requested, disable certificate validation */
+ if (t->connection_data.use_ssl) {
+ int flags;
+
+ if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
+ goto on_error;
+ }
+
+ /* If we have a credential on the subtransport, apply it to the request */
+ if (t->cred &&
+ t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC &&
+ apply_basic_credential(s->request, t->cred) < 0)
+ goto on_error;
+ else if (t->cred &&
+ t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
+ t->auth_mechanism == GIT_WINHTTP_AUTH_NEGOTIATE &&
+ apply_default_credentials(s->request) < 0)
+ goto on_error;
+
+ /* If no other credentials have been applied and the URL has username and
+ * password, use those */
+ if (!t->cred && t->connection_data.user && t->connection_data.pass) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0)
+ goto on_error;
+ if (apply_basic_credential(s->request, t->url_cred) < 0)
+ goto on_error;
+ }
+
+ /* We've done everything up to calling WinHttpSendRequest. */
+
+ error = 0;
+
+on_error:
+ if (error < 0)
+ winhttp_stream_close(s);
+
+ git__free(proxy_url);
+ git_buf_free(&buf);
+ return error;
+}
+
+static int parse_unauthorized_response(
+ HINTERNET request,
+ int *allowed_types,
+ int *auth_mechanism)
+{
+ DWORD supported, first, target;
+
+ *allowed_types = 0;
+ *auth_mechanism = 0;
+
+ /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
+ * We can assume this was already done, since we know we are unauthorized.
+ */
+ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
+ giterr_set(GITERR_OS, "Failed to parse supported auth schemes");
+ return -1;
+ }
+
+ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
+
+ if ((WINHTTP_AUTH_SCHEME_NTLM & supported) ||
+ (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported)) {
+ *allowed_types |= GIT_CREDTYPE_DEFAULT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_NEGOTIATE;
+ }
+
+ return 0;
+}
+
+static int write_chunk(HINTERNET request, const char *buffer, size_t len)
+{
+ DWORD bytes_written;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (!WinHttpWriteData(request,
+ git_buf_cstr(&buf), (DWORD)git_buf_len(&buf),
+ &bytes_written)) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_OS, "Failed to write chunk header");
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (!WinHttpWriteData(request,
+ buffer, (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk");
+ return -1;
+ }
+
+ /* Chunk footer */
+ if (!WinHttpWriteData(request,
+ "\r\n", 2,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk footer");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int winhttp_close_connection(winhttp_subtransport *t)
+{
+ int ret = 0;
+
+ if (t->connection) {
+ if (!WinHttpCloseHandle(t->connection)) {
+ giterr_set(GITERR_OS, "Unable to close connection");
+ ret = -1;
+ }
+
+ t->connection = NULL;
+ }
+
+ if (t->session) {
+ if (!WinHttpCloseHandle(t->session)) {
+ giterr_set(GITERR_OS, "Unable to close session");
+ ret = -1;
+ }
+
+ t->session = NULL;
+ }
+
+ return ret;
+}
+
+static int user_agent(git_buf *ua)
+{
+ const char *custom = git_libgit2__user_agent();
+
+ git_buf_clear(ua);
+ git_buf_PUTS(ua, "git/1.0 (");
+
+ if (custom)
+ git_buf_puts(ua, custom);
+ else
+ git_buf_PUTS(ua, "libgit2 " LIBGIT2_VERSION);
+
+ return git_buf_putc(ua, ')');
+}
+
+static int winhttp_connect(
+ winhttp_subtransport *t)
+{
+ wchar_t *wide_host;
+ int32_t port;
+ wchar_t *wide_ua;
+ git_buf ua = GIT_BUF_INIT;
+ int error = -1;
+ int default_timeout = TIMEOUT_INFINITE;
+ int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+
+ t->session = NULL;
+ t->connection = NULL;
+
+ /* Prepare port */
+ if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0)
+ return -1;
+
+ /* Prepare host */
+ if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert host to wide characters");
+ return -1;
+ }
+
+ if ((error = user_agent(&ua)) < 0) {
+ git__free(wide_host);
+ return error;
+ }
+
+ if (git__utf8_to_16_alloc(&wide_ua, git_buf_cstr(&ua)) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert host to wide characters");
+ git__free(wide_host);
+ git_buf_free(&ua);
+ return -1;
+ }
+
+ git_buf_free(&ua);
+
+ /* Establish session */
+ t->session = WinHttpOpen(
+ wide_ua,
+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+
+ if (!t->session) {
+ giterr_set(GITERR_OS, "Failed to init WinHTTP");
+ goto on_error;
+ }
+
+ if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
+ giterr_set(GITERR_OS, "Failed to set timeouts for WinHTTP");
+ goto on_error;
+ }
+
+
+ /* Establish connection */
+ t->connection = WinHttpConnect(
+ t->session,
+ wide_host,
+ (INTERNET_PORT) port,
+ 0);
+
+ if (!t->connection) {
+ giterr_set(GITERR_OS, "Failed to connect to host");
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ if (error < 0)
+ winhttp_close_connection(t);
+
+ git__free(wide_host);
+ git__free(wide_ua);
+
+ return error;
+}
+
+static int do_send_request(winhttp_stream *s, size_t len, int ignore_length)
+{
+ if (ignore_length) {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
+ return -1;
+ }
+ } else {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ len, 0)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int send_request(winhttp_stream *s, size_t len, int ignore_length)
+{
+ int request_failed = 0, cert_valid = 1, error = 0;
+ DWORD ignore_flags;
+
+ if ((error = do_send_request(s, len, ignore_length)) < 0)
+ request_failed = 1;
+
+ if (request_failed) {
+ if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) {
+ giterr_set(GITERR_OS, "failed to send request");
+ return -1;
+ } else {
+ cert_valid = 0;
+ }
+ }
+
+ giterr_clear();
+ if ((error = certificate_check(s, cert_valid)) < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_OS, "user cancelled certificate check");
+
+ return error;
+ }
+
+ /* if neither the request nor the certificate check returned errors, we're done */
+ if (!request_failed)
+ return 0;
+
+ ignore_flags = no_check_cert_flags;
+
+ if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
+ giterr_set(GITERR_OS, "failed to set security options");
+ return -1;
+ }
+
+ if ((error = do_send_request(s, len, ignore_length)) < 0)
+ giterr_set(GITERR_OS, "failed to send request");
+
+ return error;
+}
+
+static int winhttp_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD dw_bytes_read;
+ char replay_count = 0;
+ int error;
+
+replay:
+ /* Enforce a reasonable cap on the number of replays */
+ if (++replay_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects or authentication replays");
+ return -1;
+ }
+
+ /* Connect if necessary */
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->received_response) {
+ DWORD status_code, status_code_length, content_type_length, bytes_written;
+ char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
+ wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
+
+ if (!s->sent_request) {
+
+ if ((error = send_request(s, s->post_body_len, 0)) < 0)
+ return error;
+
+ s->sent_request = 1;
+ }
+
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (!WinHttpWriteData(s->request,
+ "0\r\n\r\n", 5,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write final chunk");
+ return -1;
+ }
+ }
+ else if (s->post_body) {
+ char *buffer;
+ DWORD len = s->post_body_len, bytes_read;
+
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
+ 0, 0, FILE_BEGIN) &&
+ NO_ERROR != GetLastError()) {
+ giterr_set(GITERR_OS, "Failed to reset file pointer");
+ return -1;
+ }
+
+ buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ while (len > 0) {
+ DWORD bytes_written;
+
+ if (!ReadFile(s->post_body, buffer,
+ min(CACHED_POST_BODY_BUF_SIZE, len),
+ &bytes_read, NULL) ||
+ !bytes_read) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to read from temp file");
+ return -1;
+ }
+
+ if (!WinHttpWriteData(s->request, buffer,
+ bytes_read, &bytes_written)) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ len -= bytes_read;
+ assert(bytes_read == bytes_written);
+ }
+
+ git__free(buffer);
+
+ /* Eagerly close the temp file */
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (!WinHttpReceiveResponse(s->request, 0)) {
+ giterr_set(GITERR_OS, "Failed to receive response");
+ return -1;
+ }
+
+ /* Verify that we got a 200 back */
+ status_code_length = sizeof(status_code);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &status_code, &status_code_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve status code");
+ return -1;
+ }
+
+ /* The implementation of WinHTTP prior to Windows 7 will not
+ * redirect to an identical URI. Some Git hosters use self-redirects
+ * as part of their DoS mitigation strategy. Check first to see if we
+ * have a redirect status code, and that we haven't already streamed
+ * a post body. (We can't replay a streamed POST.) */
+ if (!s->chunked &&
+ (HTTP_STATUS_MOVED == status_code ||
+ HTTP_STATUS_REDIRECT == status_code ||
+ (HTTP_STATUS_REDIRECT_METHOD == status_code &&
+ get_verb == s->verb) ||
+ HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
+
+ /* Check for Windows 7. This workaround is only necessary on
+ * Windows Vista and earlier. Windows 7 is version 6.1. */
+ wchar_t *location;
+ DWORD location_length;
+ char *location8;
+
+ /* OK, fetch the Location header from the redirect. */
+ if (WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WINHTTP_NO_OUTPUT_BUFFER,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ return -1;
+ }
+
+ location = git__malloc(location_length);
+ GITERR_CHECK_ALLOC(location);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ location,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ git__free(location);
+ return -1;
+ }
+
+ /* Convert the Location header to UTF-8 */
+ if (git__utf16_to_8_alloc(&location8, location) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert Location header to UTF-8");
+ git__free(location);
+ return -1;
+ }
+
+ git__free(location);
+
+ /* Replay the request */
+ winhttp_stream_close(s);
+
+ if (!git__prefixcmp_icase(location8, prefix_https)) {
+ /* Upgrade to secure connection; disconnect and start over */
+ if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
+ git__free(location8);
+ return -1;
+ }
+
+ winhttp_close_connection(t);
+
+ if (winhttp_connect(t) < 0)
+ return -1;
+ }
+
+ git__free(location8);
+ goto replay;
+ }
+
+ /* Handle proxy authentication failures */
+ if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
+ int allowed_types;
+
+ if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
+ return -1;
+
+ /* TODO: extract the username from the url, no payload? */
+ if (t->owner->proxy.credentials) {
+ int cred_error = 1;
+ cred_error = t->owner->proxy.credentials(&t->proxy_cred, t->owner->proxy.url, NULL, allowed_types, NULL);
+
+ if (cred_error < 0)
+ return cred_error;
+ }
+
+ winhttp_stream_close(s);
+ goto replay;
+ }
+
+ /* Handle authentication failures */
+ if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) {
+ int allowed_types;
+
+ if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
+ return -1;
+
+ if (allowed_types) {
+ int cred_error = 1;
+
+ git_cred_free(t->cred);
+ t->cred = NULL;
+ /* Start with the user-supplied credential callback, if present */
+ if (t->owner->cred_acquire_cb) {
+ cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, t->owner->cred_acquire_payload);
+
+ /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
+ if (cred_error == GIT_PASSTHROUGH)
+ cred_error = 1;
+ else if (cred_error < 0)
+ return cred_error;
+ }
+
+ /* Invoke the fallback credentials acquisition callback if necessary */
+ if (cred_error > 0) {
+ cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, NULL);
+
+ if (cred_error < 0)
+ return cred_error;
+ }
+
+ if (!cred_error) {
+ assert(t->cred);
+
+ winhttp_stream_close(s);
+
+ /* Successfully acquired a credential */
+ goto replay;
+ }
+ }
+ }
+
+ if (HTTP_STATUS_OK != status_code) {
+ giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
+ return -1;
+ }
+
+ /* Verify that we got the correct content-type back */
+ if (post_verb == s->verb)
+ p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
+ else
+ p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
+
+ if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters");
+ return -1;
+ }
+
+ content_type_length = sizeof(content_type);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_CONTENT_TYPE,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &content_type, &content_type_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve response content-type");
+ return -1;
+ }
+
+ if (wcscmp(expected_content_type, content_type)) {
+ giterr_set(GITERR_NET, "Received unexpected content-type");
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ if (!WinHttpReadData(s->request,
+ (LPVOID)buffer,
+ (DWORD)buf_size,
+ &dw_bytes_read))
+ {
+ giterr_set(GITERR_OS, "Failed to read data");
+ return -1;
+ }
+
+ *bytes_read = dw_bytes_read;
+
+ return 0;
+}
+
+static int winhttp_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ DWORD bytes_written;
+ int error;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* This implementation of write permits only a single call. */
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ if ((error = send_request(s, len, 0)) < 0)
+ return error;
+
+ s->sent_request = 1;
+
+ if (!WinHttpWriteData(s->request,
+ (LPCVOID)buffer,
+ (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
+{
+ UUID uuid;
+ RPC_STATUS status = UuidCreate(&uuid);
+ int result;
+
+ if (RPC_S_OK != status &&
+ RPC_S_UUID_LOCAL_ONLY != status &&
+ RPC_S_UUID_NO_ADDRESS != status) {
+ giterr_set(GITERR_NET, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer too small for name of temp file");
+ return -1;
+ }
+
+#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
+ result = swprintf_s(buffer, buffer_len_cch,
+#else
+ result = wsprintfW(buffer,
+#endif
+ L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+ uuid.Data1, uuid.Data2, uuid.Data3,
+ uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
+ uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
+
+ if (result < UUID_LENGTH_CCH) {
+ giterr_set(GITERR_OS, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ size_t len;
+
+ if (!GetTempPathW(buffer_len_cch, buffer)) {
+ giterr_set(GITERR_OS, "Failed to get temp path");
+ return -1;
+ }
+
+ len = wcslen(buffer);
+
+ if (buffer[len - 1] != '\\' && len < buffer_len_cch)
+ buffer[len++] = '\\';
+
+ if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int winhttp_stream_write_buffered(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* Buffer the payload, using a temporary file so we delegate
+ * memory management of the data to the operating system. */
+ if (!s->post_body) {
+ wchar_t temp_path[MAX_PATH + 1];
+
+ if (get_temp_file(temp_path, MAX_PATH + 1) < 0)
+ return -1;
+
+ s->post_body = CreateFileW(temp_path,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_DELETE, NULL,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == s->post_body) {
+ s->post_body = NULL;
+ giterr_set(GITERR_OS, "Failed to create temporary file");
+ return -1;
+ }
+ }
+
+ if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {
+ giterr_set(GITERR_OS, "Failed to write to temporary file");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ s->post_body_len += bytes_written;
+
+ return 0;
+}
+
+static int winhttp_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ int error;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->sent_request) {
+ /* Send Transfer-Encoding: chunked header */
+ if (!WinHttpAddRequestHeaders(s->request,
+ transfer_encoding, (ULONG) -1L,
+ WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ return -1;
+ }
+
+ if ((error = send_request(s, 0, 1)) < 0)
+ return error;
+
+ s->sent_request = 1;
+ }
+
+ if (len > CACHED_POST_BODY_BUF_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(s->request, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Is there any remaining data from the source? */
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = (unsigned int)len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void winhttp_stream_free(git_smart_subtransport_stream *stream)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+
+ winhttp_stream_close(s);
+ git__free(s);
+}
+
+static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(1, sizeof(winhttp_stream));
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = winhttp_stream_read;
+ s->parent.write = winhttp_stream_write_single;
+ s->parent.free = winhttp_stream_free;
+
+ *stream = s;
+
+ return 0;
+}
+
+static int winhttp_uploadpack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ GIT_UNUSED(t);
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_uploadpack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ GIT_UNUSED(t);
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ GIT_UNUSED(t);
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ GIT_UNUSED(t);
+
+ /* WinHTTP only supports Transfer-Encoding: chunked
+ * on Windows Vista (NT 6.0) and higher. */
+ s->chunked = git_has_win32_version(6, 0, 0);
+
+ if (s->chunked)
+ s->parent.write = winhttp_stream_write_chunked;
+ else
+ s->parent.write = winhttp_stream_write_buffered;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ winhttp_stream *s;
+ int ret = -1;
+
+ if (!t->connection)
+ if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 ||
+ (ret = winhttp_connect(t)) < 0)
+ return ret;
+
+ if (winhttp_stream_alloc(t, &s) < 0)
+ return -1;
+
+ if (!stream)
+ return -1;
+
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ ret = winhttp_uploadpack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_UPLOADPACK:
+ ret = winhttp_uploadpack(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ ret = winhttp_receivepack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK:
+ ret = winhttp_receivepack(t, s);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ if (!ret)
+ *stream = &s->parent;
+
+ return ret;
+}
+
+static int winhttp_close(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+
+ gitno_connection_data_free_ptrs(&t->connection_data);
+ memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
+ gitno_connection_data_free_ptrs(&t->proxy_connection_data);
+ memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data));
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->proxy_cred) {
+ t->proxy_cred->free(t->proxy_cred);
+ t->proxy_cred = NULL;
+ }
+
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
+ return winhttp_close_connection(t);
+}
+
+static void winhttp_free(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+
+ winhttp_close(subtransport);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
+{
+ winhttp_subtransport *t;
+
+ GIT_UNUSED(param);
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(1, sizeof(winhttp_subtransport));
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = winhttp_action;
+ t->parent.close = winhttp_close;
+ t->parent.free = winhttp_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_WINHTTP */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "tree-cache.h"
+#include "pool.h"
+#include "tree.h"
+
+static git_tree_cache *find_child(
+ const git_tree_cache *tree, const char *path, const char *end)
+{
+ size_t i, dirlen = end ? (size_t)(end - path) : strlen(path);
+
+ for (i = 0; i < tree->children_count; ++i) {
+ git_tree_cache *child = tree->children[i];
+
+ if (child->namelen == dirlen && !memcmp(path, child->name, dirlen))
+ return child;
+ }
+
+ return NULL;
+}
+
+void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path)
+{
+ const char *ptr = path, *end;
+
+ if (tree == NULL)
+ return;
+
+ tree->entry_count = -1;
+
+ while (ptr != NULL) {
+ end = strchr(ptr, '/');
+
+ if (end == NULL) /* End of path */
+ break;
+
+ tree = find_child(tree, ptr, end);
+ if (tree == NULL) /* We don't have that tree */
+ return;
+
+ tree->entry_count = -1;
+ ptr = end + 1;
+ }
+}
+
+const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path)
+{
+ const char *ptr = path, *end;
+
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ while (1) {
+ end = strchr(ptr, '/');
+
+ tree = find_child(tree, ptr, end);
+ if (tree == NULL) /* Can't find it */
+ return NULL;
+
+ if (end == NULL || *end + 1 == '\0')
+ return tree;
+
+ ptr = end + 1;
+ }
+}
+
+static int read_tree_internal(git_tree_cache **out,
+ const char **buffer_in, const char *buffer_end,
+ git_pool *pool)
+{
+ git_tree_cache *tree = NULL;
+ const char *name_start, *buffer;
+ int count;
+
+ buffer = name_start = *buffer_in;
+
+ if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL)
+ goto corrupted;
+
+ if (++buffer >= buffer_end)
+ goto corrupted;
+
+ if (git_tree_cache_new(&tree, name_start, pool) < 0)
+ return -1;
+
+ /* Blank-terminated ASCII decimal number of entries in this tree */
+ if (git__strtol32(&count, buffer, &buffer, 10) < 0)
+ goto corrupted;
+
+ tree->entry_count = count;
+
+ if (*buffer != ' ' || ++buffer >= buffer_end)
+ goto corrupted;
+
+ /* Number of children of the tree, newline-terminated */
+ if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0)
+ goto corrupted;
+
+ tree->children_count = count;
+
+ if (*buffer != '\n' || ++buffer > buffer_end)
+ goto corrupted;
+
+ /* The SHA1 is only there if it's not invalidated */
+ if (tree->entry_count >= 0) {
+ /* 160-bit SHA-1 for this tree and it's children */
+ if (buffer + GIT_OID_RAWSZ > buffer_end)
+ goto corrupted;
+
+ git_oid_fromraw(&tree->oid, (const unsigned char *)buffer);
+ buffer += GIT_OID_RAWSZ;
+ }
+
+ /* Parse children: */
+ if (tree->children_count > 0) {
+ unsigned int i;
+
+ tree->children = git_pool_malloc(pool, tree->children_count * sizeof(git_tree_cache *));
+ GITERR_CHECK_ALLOC(tree->children);
+
+ memset(tree->children, 0x0, tree->children_count * sizeof(git_tree_cache *));
+
+ for (i = 0; i < tree->children_count; ++i) {
+ if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0)
+ goto corrupted;
+ }
+ }
+
+ *buffer_in = buffer;
+ *out = tree;
+ return 0;
+
+ corrupted:
+ giterr_set(GITERR_INDEX, "Corrupted TREE extension in index");
+ return -1;
+}
+
+int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool)
+{
+ const char *buffer_end = buffer + buffer_size;
+
+ if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0)
+ return -1;
+
+ if (buffer < buffer_end) {
+ giterr_set(GITERR_INDEX, "Corrupted TREE extension in index (unexpected trailing data)");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_pool *pool)
+{
+ git_repository *repo;
+ size_t i, j, nentries, ntrees;
+ int error;
+
+ repo = git_tree_owner(tree);
+
+ git_oid_cpy(&cache->oid, git_tree_id(tree));
+ nentries = git_tree_entrycount(tree);
+
+ /*
+ * We make sure we know how many trees we need to allocate for
+ * so we don't have to realloc and change the pointers for the
+ * parents.
+ */
+ ntrees = 0;
+ for (i = 0; i < nentries; i++) {
+ const git_tree_entry *entry;
+
+ entry = git_tree_entry_byindex(tree, i);
+ if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE)
+ ntrees++;
+ }
+
+ cache->children_count = ntrees;
+ cache->children = git_pool_mallocz(pool, ntrees * sizeof(git_tree_cache *));
+ GITERR_CHECK_ALLOC(cache->children);
+
+ j = 0;
+ for (i = 0; i < nentries; i++) {
+ const git_tree_entry *entry;
+ git_tree *subtree;
+
+ entry = git_tree_entry_byindex(tree, i);
+ if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) {
+ cache->entry_count++;
+ continue;
+ }
+
+ if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0)
+ return error;
+
+ if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0)
+ return error;
+
+ error = read_tree_recursive(cache->children[j], subtree, pool);
+ git_tree_free(subtree);
+ cache->entry_count += cache->children[j]->entry_count;
+ j++;
+
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool)
+{
+ int error;
+ git_tree_cache *cache;
+
+ if ((error = git_tree_cache_new(&cache, "", pool)) < 0)
+ return error;
+
+ if ((error = read_tree_recursive(cache, tree, pool)) < 0)
+ return error;
+
+ *out = cache;
+ return 0;
+}
+
+int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool)
+{
+ size_t name_len;
+ git_tree_cache *tree;
+
+ name_len = strlen(name);
+ tree = git_pool_malloc(pool, sizeof(git_tree_cache) + name_len + 1);
+ GITERR_CHECK_ALLOC(tree);
+
+ memset(tree, 0x0, sizeof(git_tree_cache));
+ /* NUL-terminated tree name */
+ tree->namelen = name_len;
+ memcpy(tree->name, name, name_len);
+ tree->name[name_len] = '\0';
+
+ *out = tree;
+ return 0;
+}
+
+static void write_tree(git_buf *out, git_tree_cache *tree)
+{
+ size_t i;
+
+ git_buf_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count);
+
+ if (tree->entry_count != -1)
+ git_buf_put(out, (const char *) &tree->oid, GIT_OID_RAWSZ);
+
+ for (i = 0; i < tree->children_count; i++)
+ write_tree(out, tree->children[i]);
+}
+
+int git_tree_cache_write(git_buf *out, git_tree_cache *tree)
+{
+ write_tree(out, tree);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_tree_cache_h__
+#define INCLUDE_tree_cache_h__
+
+#include "common.h"
+#include "pool.h"
+#include "buffer.h"
+#include "git2/oid.h"
+
+typedef struct git_tree_cache {
+ struct git_tree_cache **children;
+ size_t children_count;
+
+ ssize_t entry_count;
+ git_oid oid;
+ size_t namelen;
+ char name[GIT_FLEX_ARRAY];
+} git_tree_cache;
+
+int git_tree_cache_write(git_buf *out, git_tree_cache *tree);
+int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool);
+void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path);
+const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path);
+int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool);
+/**
+ * Read a tree as the root of the tree cache (like for `git read-tree`)
+ */
+int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool);
+void git_tree_cache_free(git_tree_cache *tree);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tree.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "fileops.h"
+#include "tree-cache.h"
+#include "index.h"
+
+#define DEFAULT_TREE_SIZE 16
+#define MAX_FILEMODE_BYTES 6
+
+#define TREE_ENTRY_CHECK_NAMELEN(n) \
+ if (n > UINT16_MAX) { giterr_set(GITERR_INVALID, "tree entry path too long"); }
+
+GIT__USE_STRMAP
+
+static bool valid_filemode(const int filemode)
+{
+ return (filemode == GIT_FILEMODE_TREE
+ || filemode == GIT_FILEMODE_BLOB
+ || filemode == GIT_FILEMODE_BLOB_EXECUTABLE
+ || filemode == GIT_FILEMODE_LINK
+ || filemode == GIT_FILEMODE_COMMIT);
+}
+
+GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
+{
+ /* Tree bits set, but it's not a commit */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE)
+ return GIT_FILEMODE_TREE;
+
+ /* If any of the x bits are set */
+ if (GIT_PERMS_IS_EXEC(filemode))
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ /* 16XXXX means commit */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT)
+ return GIT_FILEMODE_COMMIT;
+
+ /* 12XXXX means symlink */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK)
+ return GIT_FILEMODE_LINK;
+
+ /* Otherwise, return a blob */
+ return GIT_FILEMODE_BLOB;
+}
+
+static int valid_entry_name(git_repository *repo, const char *filename)
+{
+ return *filename != '\0' &&
+ git_path_isvalid(repo, filename,
+ GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH);
+}
+
+static int entry_sort_cmp(const void *a, const void *b)
+{
+ const git_tree_entry *e1 = (const git_tree_entry *)a;
+ const git_tree_entry *e2 = (const git_tree_entry *)b;
+
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncmp);
+}
+
+int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
+ return entry_sort_cmp(e1, e2);
+}
+
+int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncasecmp);
+}
+
+/**
+ * Allocate a new self-contained entry, with enough space after it to
+ * store the filename and the id.
+ */
+static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id)
+{
+ git_tree_entry *entry = NULL;
+ size_t tree_len;
+
+ TREE_ENTRY_CHECK_NAMELEN(filename_len);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) ||
+ GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ))
+ return NULL;
+
+ entry = git__calloc(1, tree_len);
+ if (!entry)
+ return NULL;
+
+ {
+ char *filename_ptr;
+ void *id_ptr;
+
+ filename_ptr = ((char *) entry) + sizeof(git_tree_entry);
+ memcpy(filename_ptr, filename, filename_len);
+ entry->filename = filename_ptr;
+
+ id_ptr = filename_ptr + filename_len + 1;
+ git_oid_cpy(id_ptr, id);
+ entry->oid = id_ptr;
+ }
+
+ entry->filename_len = (uint16_t)filename_len;
+
+ return entry;
+}
+
+struct tree_key_search {
+ const char *filename;
+ uint16_t filename_len;
+};
+
+static int homing_search_cmp(const void *key, const void *array_member)
+{
+ const struct tree_key_search *ksearch = key;
+ const git_tree_entry *entry = array_member;
+
+ const uint16_t len1 = ksearch->filename_len;
+ const uint16_t len2 = entry->filename_len;
+
+ return memcmp(
+ ksearch->filename,
+ entry->filename,
+ len1 < len2 ? len1 : len2
+ );
+}
+
+/*
+ * Search for an entry in a given tree.
+ *
+ * Note that this search is performed in two steps because
+ * of the way tree entries are sorted internally in git:
+ *
+ * Entries in a tree are not sorted alphabetically; two entries
+ * with the same root prefix will have different positions
+ * depending on whether they are folders (subtrees) or normal files.
+ *
+ * Consequently, it is not possible to find an entry on the tree
+ * with a binary search if you don't know whether the filename
+ * you're looking for is a folder or a normal file.
+ *
+ * To work around this, we first perform a homing binary search
+ * on the tree, using the minimal length root prefix of our filename.
+ * Once the comparisons for this homing search start becoming
+ * ambiguous because of folder vs file sorting, we look linearly
+ * around the area for our target file.
+ */
+static int tree_key_search(
+ size_t *at_pos,
+ const git_tree *tree,
+ const char *filename,
+ size_t filename_len)
+{
+ struct tree_key_search ksearch;
+ const git_tree_entry *entry;
+ size_t homing, i;
+
+ TREE_ENTRY_CHECK_NAMELEN(filename_len);
+
+ ksearch.filename = filename;
+ ksearch.filename_len = (uint16_t)filename_len;
+
+ /* Initial homing search; find an entry on the tree with
+ * the same prefix as the filename we're looking for */
+
+ if (git_array_search(&homing,
+ tree->entries, &homing_search_cmp, &ksearch) < 0)
+ return GIT_ENOTFOUND; /* just a signal error; not passed back to user */
+
+ /* We found a common prefix. Look forward as long as
+ * there are entries that share the common prefix */
+ for (i = homing; i < tree->entries.size; ++i) {
+ entry = git_array_get(tree->entries, i);
+
+ if (homing_search_cmp(&ksearch, entry) < 0)
+ break;
+
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ }
+
+ /* If we haven't found our filename yet, look backwards
+ * too as long as we have entries with the same prefix */
+ if (homing > 0) {
+ i = homing - 1;
+
+ do {
+ entry = git_array_get(tree->entries, i);
+
+ if (homing_search_cmp(&ksearch, entry) > 0)
+ break;
+
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ } while (i-- > 0);
+ }
+
+ /* The filename doesn't exist at all */
+ return GIT_ENOTFOUND;
+}
+
+void git_tree_entry_free(git_tree_entry *entry)
+{
+ if (entry == NULL)
+ return;
+
+ git__free(entry);
+}
+
+int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source)
+{
+ git_tree_entry *cpy;
+
+ assert(source);
+
+ cpy = alloc_entry(source->filename, source->filename_len, source->oid);
+ if (cpy == NULL)
+ return -1;
+
+ cpy->attr = source->attr;
+
+ *dest = cpy;
+ return 0;
+}
+
+void git_tree__free(void *_tree)
+{
+ git_tree *tree = _tree;
+
+ git_odb_object_free(tree->odb_obj);
+ git_array_clear(tree->entries);
+ git__free(tree);
+}
+
+git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
+{
+ return normalize_filemode(entry->attr);
+}
+
+git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry)
+{
+ return entry->attr;
+}
+
+const char *git_tree_entry_name(const git_tree_entry *entry)
+{
+ assert(entry);
+ return entry->filename;
+}
+
+const git_oid *git_tree_entry_id(const git_tree_entry *entry)
+{
+ assert(entry);
+ return entry->oid;
+}
+
+git_otype git_tree_entry_type(const git_tree_entry *entry)
+{
+ assert(entry);
+
+ if (S_ISGITLINK(entry->attr))
+ return GIT_OBJ_COMMIT;
+ else if (S_ISDIR(entry->attr))
+ return GIT_OBJ_TREE;
+ else
+ return GIT_OBJ_BLOB;
+}
+
+int git_tree_entry_to_object(
+ git_object **object_out,
+ git_repository *repo,
+ const git_tree_entry *entry)
+{
+ assert(entry && object_out);
+ return git_object_lookup(object_out, repo, entry->oid, GIT_OBJ_ANY);
+}
+
+static const git_tree_entry *entry_fromname(
+ const git_tree *tree, const char *name, size_t name_len)
+{
+ size_t idx;
+
+ if (tree_key_search(&idx, tree, name, name_len) < 0)
+ return NULL;
+
+ return git_array_get(tree->entries, idx);
+}
+
+const git_tree_entry *git_tree_entry_byname(
+ const git_tree *tree, const char *filename)
+{
+ assert(tree && filename);
+
+ return entry_fromname(tree, filename, strlen(filename));
+}
+
+const git_tree_entry *git_tree_entry_byindex(
+ const git_tree *tree, size_t idx)
+{
+ assert(tree);
+ return git_array_get(tree->entries, idx);
+}
+
+const git_tree_entry *git_tree_entry_byid(
+ const git_tree *tree, const git_oid *id)
+{
+ size_t i;
+ const git_tree_entry *e;
+
+ assert(tree);
+
+ git_array_foreach(tree->entries, i, e) {
+ if (memcmp(&e->oid->id, &id->id, sizeof(id->id)) == 0)
+ return e;
+ }
+
+ return NULL;
+}
+
+int git_tree__prefix_position(const git_tree *tree, const char *path)
+{
+ struct tree_key_search ksearch;
+ size_t at_pos, path_len;
+
+ if (!path)
+ return 0;
+
+ path_len = strlen(path);
+ TREE_ENTRY_CHECK_NAMELEN(path_len);
+
+ ksearch.filename = path;
+ ksearch.filename_len = (uint16_t)path_len;
+
+ /* Find tree entry with appropriate prefix */
+ git_array_search(
+ &at_pos, tree->entries, &homing_search_cmp, &ksearch);
+
+ for (; at_pos < tree->entries.size; ++at_pos) {
+ const git_tree_entry *entry = git_array_get(tree->entries, at_pos);
+ if (homing_search_cmp(&ksearch, entry) < 0)
+ break;
+ }
+
+ for (; at_pos > 0; --at_pos) {
+ const git_tree_entry *entry =
+ git_array_get(tree->entries, at_pos - 1);
+
+ if (homing_search_cmp(&ksearch, entry) > 0)
+ break;
+ }
+
+ return (int)at_pos;
+}
+
+size_t git_tree_entrycount(const git_tree *tree)
+{
+ assert(tree);
+ return tree->entries.size;
+}
+
+unsigned int git_treebuilder_entrycount(git_treebuilder *bld)
+{
+ assert(bld);
+
+ return git_strmap_num_entries(bld->map);
+}
+
+static int tree_error(const char *str, const char *path)
+{
+ if (path)
+ giterr_set(GITERR_TREE, "%s - %s", str, path);
+ else
+ giterr_set(GITERR_TREE, "%s", str);
+ return -1;
+}
+
+static int parse_mode(unsigned int *modep, const char *buffer, const char **buffer_out)
+{
+ unsigned char c;
+ unsigned int mode = 0;
+
+ if (*buffer == ' ')
+ return -1;
+
+ while ((c = *buffer++) != ' ') {
+ if (c < '0' || c > '7')
+ return -1;
+ mode = (mode << 3) + (c - '0');
+ }
+ *modep = mode;
+ *buffer_out = buffer;
+
+ return 0;
+}
+
+int git_tree__parse(void *_tree, git_odb_object *odb_obj)
+{
+ git_tree *tree = _tree;
+ const char *buffer;
+ const char *buffer_end;
+
+ if (git_odb_object_dup(&tree->odb_obj, odb_obj) < 0)
+ return -1;
+
+ buffer = git_odb_object_data(tree->odb_obj);
+ buffer_end = buffer + git_odb_object_size(tree->odb_obj);
+
+ git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE);
+ GITERR_CHECK_ARRAY(tree->entries);
+
+ while (buffer < buffer_end) {
+ git_tree_entry *entry;
+ size_t filename_len;
+ const char *nul;
+ unsigned int attr;
+
+ if (parse_mode(&attr, buffer, &buffer) < 0 || !buffer)
+ return tree_error("Failed to parse tree. Can't parse filemode", NULL);
+
+ if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL)
+ return tree_error("Failed to parse tree. Object is corrupted", NULL);
+
+ if ((filename_len = nul - buffer) == 0)
+ return tree_error("Failed to parse tree. Can't parse filename", NULL);
+
+ if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ)
+ return tree_error("Failed to parse tree. Can't parse OID", NULL);
+
+ /* Allocate the entry */
+ {
+ entry = git_array_alloc(tree->entries);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->attr = attr;
+ entry->filename_len = filename_len;
+ entry->filename = buffer;
+ entry->oid = (git_oid *) ((char *) buffer + filename_len + 1);
+ }
+
+ buffer += filename_len + 1;
+ buffer += GIT_OID_RAWSZ;
+ }
+
+ return 0;
+}
+
+static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
+{
+ size_t dirlen, i, entries = git_index_entrycount(index);
+
+ dirlen = strlen(dirname);
+ for (i = start; i < entries; ++i) {
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ if (strlen(entry->path) < dirlen ||
+ memcmp(entry->path, dirname, dirlen) ||
+ (dirlen > 0 && entry->path[dirlen] != '/')) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+static int append_entry(
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
+{
+ git_tree_entry *entry;
+ int error = 0;
+
+ if (!valid_entry_name(bld->repo, filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
+
+ entry = alloc_entry(filename, strlen(filename), id);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->attr = (uint16_t)filemode;
+
+ git_strmap_insert(bld->map, entry->filename, entry, error);
+ if (error < 0) {
+ git_tree_entry_free(entry);
+ giterr_set(GITERR_TREE, "failed to append entry %s to the tree builder", filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int write_tree(
+ git_oid *oid,
+ git_repository *repo,
+ git_index *index,
+ const char *dirname,
+ size_t start)
+{
+ git_treebuilder *bld = NULL;
+ size_t i, entries = git_index_entrycount(index);
+ int error;
+ size_t dirname_len = strlen(dirname);
+ const git_tree_cache *cache;
+
+ cache = git_tree_cache_get(index->tree, dirname);
+ if (cache != NULL && cache->entry_count >= 0){
+ git_oid_cpy(oid, &cache->oid);
+ return (int)find_next_dir(dirname, index, start);
+ }
+
+ if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL)
+ return -1;
+
+ /*
+ * This loop is unfortunate, but necessary. The index doesn't have
+ * any directores, so we need to handle that manually, and we
+ * need to keep track of the current position.
+ */
+ for (i = start; i < entries; ++i) {
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ const char *filename, *next_slash;
+
+ /*
+ * If we've left our (sub)tree, exit the loop and return. The
+ * first check is an early out (and security for the
+ * third). The second check is a simple prefix comparison. The
+ * third check catches situations where there is a directory
+ * win32/sys and a file win32mmap.c. Without it, the following
+ * code believes there is a file win32/mmap.c
+ */
+ if (strlen(entry->path) < dirname_len ||
+ memcmp(entry->path, dirname, dirname_len) ||
+ (dirname_len > 0 && entry->path[dirname_len] != '/')) {
+ break;
+ }
+
+ filename = entry->path + dirname_len;
+ if (*filename == '/')
+ filename++;
+ next_slash = strchr(filename, '/');
+ if (next_slash) {
+ git_oid sub_oid;
+ int written;
+ char *subdir, *last_comp;
+
+ subdir = git__strndup(entry->path, next_slash - entry->path);
+ GITERR_CHECK_ALLOC(subdir);
+
+ /* Write out the subtree */
+ written = write_tree(&sub_oid, repo, index, subdir, i);
+ if (written < 0) {
+ git__free(subdir);
+ goto on_error;
+ } else {
+ i = written - 1; /* -1 because of the loop increment */
+ }
+
+ /*
+ * We need to figure out what we want toinsert
+ * into this tree. If we're traversing
+ * deps/zlib/, then we only want to write
+ * 'zlib' into the tree.
+ */
+ last_comp = strrchr(subdir, '/');
+ if (last_comp) {
+ last_comp++; /* Get rid of the '/' */
+ } else {
+ last_comp = subdir;
+ }
+
+ error = append_entry(bld, last_comp, &sub_oid, S_IFDIR);
+ git__free(subdir);
+ if (error < 0)
+ goto on_error;
+ } else {
+ error = append_entry(bld, filename, &entry->id, entry->mode);
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ if (git_treebuilder_write(oid, bld) < 0)
+ goto on_error;
+
+ git_treebuilder_free(bld);
+ return (int)i;
+
+on_error:
+ git_treebuilder_free(bld);
+ return -1;
+}
+
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo)
+{
+ int ret;
+ git_tree *tree;
+ bool old_ignore_case = false;
+
+ assert(oid && index && repo);
+
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_INDEX,
+ "Cannot create a tree from a not fully merged index.");
+ return GIT_EUNMERGED;
+ }
+
+ if (index->tree != NULL && index->tree->entry_count >= 0) {
+ git_oid_cpy(oid, &index->tree->oid);
+ return 0;
+ }
+
+ /* The tree cache didn't help us; we'll have to write
+ * out a tree. If the index is ignore_case, we must
+ * make it case-sensitive for the duration of the tree-write
+ * operation. */
+
+ if (index->ignore_case) {
+ old_ignore_case = true;
+ git_index__set_ignore_case(index, false);
+ }
+
+ ret = write_tree(oid, repo, index, "", 0);
+
+ if (old_ignore_case)
+ git_index__set_ignore_case(index, true);
+
+ index->tree = NULL;
+
+ if (ret < 0)
+ return ret;
+
+ git_pool_clear(&index->tree_pool);
+
+ if ((ret = git_tree_lookup(&tree, repo, oid)) < 0)
+ return ret;
+
+ /* Read the tree cache into the index */
+ ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool);
+ git_tree_free(tree);
+
+ return ret;
+}
+
+int git_treebuilder_new(
+ git_treebuilder **builder_p,
+ git_repository *repo,
+ const git_tree *source)
+{
+ git_treebuilder *bld;
+ size_t i;
+
+ assert(builder_p && repo);
+
+ bld = git__calloc(1, sizeof(git_treebuilder));
+ GITERR_CHECK_ALLOC(bld);
+
+ bld->repo = repo;
+
+ if (git_strmap_alloc(&bld->map) < 0) {
+ git__free(bld);
+ return -1;
+ }
+
+ if (source != NULL) {
+ git_tree_entry *entry_src;
+
+ git_array_foreach(source->entries, i, entry_src) {
+ if (append_entry(
+ bld, entry_src->filename,
+ entry_src->oid,
+ entry_src->attr) < 0)
+ goto on_error;
+ }
+ }
+
+ *builder_p = bld;
+ return 0;
+
+on_error:
+ git_treebuilder_free(bld);
+ return -1;
+}
+
+static git_otype otype_from_mode(git_filemode_t filemode)
+{
+ switch (filemode) {
+ case GIT_FILEMODE_TREE:
+ return GIT_OBJ_TREE;
+ case GIT_FILEMODE_COMMIT:
+ return GIT_OBJ_COMMIT;
+ default:
+ return GIT_OBJ_BLOB;
+ }
+}
+
+int git_treebuilder_insert(
+ const git_tree_entry **entry_out,
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
+{
+ git_tree_entry *entry;
+ int error;
+ git_strmap_iter pos;
+
+ assert(bld && id && filename);
+
+ if (!valid_filemode(filemode))
+ return tree_error("Failed to insert entry. Invalid filemode for file", filename);
+
+ if (!valid_entry_name(bld->repo, filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
+
+ if (filemode != GIT_FILEMODE_COMMIT &&
+ !git_object__is_valid(bld->repo, id, otype_from_mode(filemode)))
+ return tree_error("Failed to insert entry; invalid object specified", filename);
+
+ pos = git_strmap_lookup_index(bld->map, filename);
+ if (git_strmap_valid_index(bld->map, pos)) {
+ entry = git_strmap_value_at(bld->map, pos);
+ git_oid_cpy((git_oid *) entry->oid, id);
+ } else {
+ entry = alloc_entry(filename, strlen(filename), id);
+ GITERR_CHECK_ALLOC(entry);
+
+ git_strmap_insert(bld->map, entry->filename, entry, error);
+
+ if (error < 0) {
+ git_tree_entry_free(entry);
+ giterr_set(GITERR_TREE, "failed to insert %s", filename);
+ return -1;
+ }
+ }
+
+ entry->attr = filemode;
+
+ if (entry_out)
+ *entry_out = entry;
+
+ return 0;
+}
+
+static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
+{
+ git_tree_entry *entry = NULL;
+ git_strmap_iter pos;
+
+ assert(bld && filename);
+
+ pos = git_strmap_lookup_index(bld->map, filename);
+ if (git_strmap_valid_index(bld->map, pos))
+ entry = git_strmap_value_at(bld->map, pos);
+
+ return entry;
+}
+
+const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename)
+{
+ return treebuilder_get(bld, filename);
+}
+
+int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
+{
+ git_tree_entry *entry = treebuilder_get(bld, filename);
+
+ if (entry == NULL)
+ return tree_error("Failed to remove entry. File isn't in the tree", filename);
+
+ git_strmap_delete(bld->map, filename);
+ git_tree_entry_free(entry);
+
+ return 0;
+}
+
+int git_treebuilder_write(git_oid *oid, git_treebuilder *bld)
+{
+ int error = 0;
+ size_t i, entrycount;
+ git_buf tree = GIT_BUF_INIT;
+ git_odb *odb;
+ git_tree_entry *entry;
+ git_vector entries;
+
+ assert(bld);
+
+ entrycount = git_strmap_num_entries(bld->map);
+ if (git_vector_init(&entries, entrycount, entry_sort_cmp) < 0)
+ return -1;
+
+ git_strmap_foreach_value(bld->map, entry, {
+ if (git_vector_insert(&entries, entry) < 0)
+ return -1;
+ });
+
+ git_vector_sort(&entries);
+
+ /* Grow the buffer beforehand to an estimated size */
+ error = git_buf_grow(&tree, entrycount * 72);
+
+ for (i = 0; i < entries.length && !error; ++i) {
+ git_tree_entry *entry = git_vector_get(&entries, i);
+
+ git_buf_printf(&tree, "%o ", entry->attr);
+ git_buf_put(&tree, entry->filename, entry->filename_len + 1);
+ git_buf_put(&tree, (char *)entry->oid->id, GIT_OID_RAWSZ);
+
+ if (git_buf_oom(&tree))
+ error = -1;
+ }
+
+
+ if (!error &&
+ !(error = git_repository_odb__weakptr(&odb, bld->repo)))
+ error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
+
+ git_buf_free(&tree);
+ git_vector_free(&entries);
+
+ return error;
+}
+
+void git_treebuilder_filter(
+ git_treebuilder *bld,
+ git_treebuilder_filter_cb filter,
+ void *payload)
+{
+ const char *filename;
+ git_tree_entry *entry;
+
+ assert(bld && filter);
+
+ git_strmap_foreach(bld->map, filename, entry, {
+ if (filter(entry, payload)) {
+ git_strmap_delete(bld->map, filename);
+ git_tree_entry_free(entry);
+ }
+ });
+}
+
+void git_treebuilder_clear(git_treebuilder *bld)
+{
+ git_tree_entry *e;
+
+ assert(bld);
+
+ git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e));
+ git_strmap_clear(bld->map);
+}
+
+void git_treebuilder_free(git_treebuilder *bld)
+{
+ if (bld == NULL)
+ return;
+
+ git_treebuilder_clear(bld);
+ git_strmap_free(bld->map);
+ git__free(bld);
+}
+
+static size_t subpath_len(const char *path)
+{
+ const char *slash_pos = strchr(path, '/');
+ if (slash_pos == NULL)
+ return strlen(path);
+
+ return slash_pos - path;
+}
+
+int git_tree_entry_bypath(
+ git_tree_entry **entry_out,
+ const git_tree *root,
+ const char *path)
+{
+ int error = 0;
+ git_tree *subtree;
+ const git_tree_entry *entry;
+ size_t filename_len;
+
+ /* Find how long is the current path component (i.e.
+ * the filename between two slashes */
+ filename_len = subpath_len(path);
+
+ if (filename_len == 0) {
+ giterr_set(GITERR_TREE, "Invalid tree path given");
+ return GIT_ENOTFOUND;
+ }
+
+ entry = entry_fromname(root, path, filename_len);
+
+ if (entry == NULL) {
+ giterr_set(GITERR_TREE,
+ "the path '%.*s' does not exist in the given tree", (int) filename_len, path);
+ return GIT_ENOTFOUND;
+ }
+
+ switch (path[filename_len]) {
+ case '/':
+ /* If there are more components in the path...
+ * then this entry *must* be a tree */
+ if (!git_tree_entry__is_tree(entry)) {
+ giterr_set(GITERR_TREE,
+ "the path '%.*s' exists but is not a tree", (int) filename_len, path);
+ return GIT_ENOTFOUND;
+ }
+
+ /* If there's only a slash left in the path, we
+ * return the current entry; otherwise, we keep
+ * walking down the path */
+ if (path[filename_len + 1] != '\0')
+ break;
+
+ case '\0':
+ /* If there are no more components in the path, return
+ * this entry */
+ return git_tree_entry_dup(entry_out, entry);
+ }
+
+ if (git_tree_lookup(&subtree, root->object.repo, entry->oid) < 0)
+ return -1;
+
+ error = git_tree_entry_bypath(
+ entry_out,
+ subtree,
+ path + filename_len + 1
+ );
+
+ git_tree_free(subtree);
+ return error;
+}
+
+static int tree_walk(
+ const git_tree *tree,
+ git_treewalk_cb callback,
+ git_buf *path,
+ void *payload,
+ bool preorder)
+{
+ int error = 0;
+ size_t i;
+ const git_tree_entry *entry;
+
+ git_array_foreach(tree->entries, i, entry) {
+ if (preorder) {
+ error = callback(path->ptr, entry, payload);
+ if (error < 0) { /* negative value stops iteration */
+ giterr_set_after_callback_function(error, "git_tree_walk");
+ break;
+ }
+ if (error > 0) { /* positive value skips this entry */
+ error = 0;
+ continue;
+ }
+ }
+
+ if (git_tree_entry__is_tree(entry)) {
+ git_tree *subtree;
+ size_t path_len = git_buf_len(path);
+
+ error = git_tree_lookup(&subtree, tree->object.repo, entry->oid);
+ if (error < 0)
+ break;
+
+ /* append the next entry to the path */
+ git_buf_puts(path, entry->filename);
+ git_buf_putc(path, '/');
+
+ if (git_buf_oom(path))
+ error = -1;
+ else
+ error = tree_walk(subtree, callback, path, payload, preorder);
+
+ git_tree_free(subtree);
+ if (error != 0)
+ break;
+
+ git_buf_truncate(path, path_len);
+ }
+
+ if (!preorder) {
+ error = callback(path->ptr, entry, payload);
+ if (error < 0) { /* negative value stops iteration */
+ giterr_set_after_callback_function(error, "git_tree_walk");
+ break;
+ }
+ error = 0;
+ }
+ }
+
+ return error;
+}
+
+int git_tree_walk(
+ const git_tree *tree,
+ git_treewalk_mode mode,
+ git_treewalk_cb callback,
+ void *payload)
+{
+ int error = 0;
+ git_buf root_path = GIT_BUF_INIT;
+
+ if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {
+ giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
+ return -1;
+ }
+
+ error = tree_walk(
+ tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE));
+
+ git_buf_free(&root_path);
+
+ return error;
+}
+
+static int compare_entries(const void *_a, const void *_b)
+{
+ const git_tree_update *a = (git_tree_update *) _a;
+ const git_tree_update *b = (git_tree_update *) _b;
+
+ return strcmp(a->path, b->path);
+}
+
+static int on_dup_entry(void **old, void *new)
+{
+ GIT_UNUSED(old); GIT_UNUSED(new);
+
+ giterr_set(GITERR_TREE, "duplicate entries given for update");
+ return -1;
+}
+
+/*
+ * We keep the previous tree and the new one at each level of the
+ * stack. When we leave a level we're done with that tree and we can
+ * write it out to the odb.
+ */
+typedef struct {
+ git_treebuilder *bld;
+ git_tree *tree;
+ char *name;
+} tree_stack_entry;
+
+/** Count how many slashes (i.e. path components) there are in this string */
+GIT_INLINE(size_t) count_slashes(const char *path)
+{
+ size_t count = 0;
+ const char *slash;
+
+ while ((slash = strchr(path, '/')) != NULL) {
+ count++;
+ path = slash + 1;
+ }
+
+ return count;
+}
+
+static bool next_component(git_buf *out, const char *in)
+{
+ const char *slash = strchr(in, '/');
+
+ git_buf_clear(out);
+
+ if (slash)
+ git_buf_put(out, in, slash - in);
+
+ return !!slash;
+}
+
+static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_buf *component)
+{
+ int error;
+ git_oid new_tree;
+
+ git_tree_free(popped->tree);
+
+ /* If the tree would be empty, remove it from the one higher up */
+ if (git_treebuilder_entrycount(popped->bld) == 0) {
+ git_treebuilder_free(popped->bld);
+ error = git_treebuilder_remove(current->bld, popped->name);
+ git__free(popped->name);
+ return error;
+ }
+
+ error = git_treebuilder_write(&new_tree, popped->bld);
+ git_treebuilder_free(popped->bld);
+
+ if (error < 0) {
+ git__free(popped->name);
+ return error;
+ }
+
+ /* We've written out the tree, now we have to put the new value into its parent */
+ git_buf_clear(component);
+ git_buf_puts(component, popped->name);
+ git__free(popped->name);
+
+ GITERR_CHECK_ALLOC(component->ptr);
+
+ /* Error out if this would create a D/F conflict in this update */
+ if (current->tree) {
+ const git_tree_entry *to_replace;
+ to_replace = git_tree_entry_byname(current->tree, component->ptr);
+ if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJ_TREE) {
+ giterr_set(GITERR_TREE, "D/F conflict when updating tree");
+ return -1;
+ }
+ }
+
+ return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE);
+}
+
+int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates)
+{
+ git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT;
+ tree_stack_entry *root_elem;
+ git_vector entries;
+ int error;
+ size_t i;
+ git_buf component = GIT_BUF_INIT;
+
+ if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0)
+ return error;
+
+ /* Sort the entries for treversal */
+ for (i = 0 ; i < nupdates; i++) {
+ if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0)
+ goto cleanup;
+ }
+
+ root_elem = git_array_alloc(stack);
+ GITERR_CHECK_ALLOC(root_elem);
+ memset(root_elem, 0, sizeof(*root_elem));
+
+ if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0)
+ goto cleanup;
+
+ if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0)
+ goto cleanup;
+
+ for (i = 0; i < nupdates; i++) {
+ const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1);
+ const git_tree_update *update = git_vector_get(&entries, i);
+ size_t common_prefix = 0, steps_up, j;
+ const char *path;
+
+ /* Figure out how much we need to change from the previous tree */
+ if (last_update)
+ common_prefix = git_path_common_dirlen(last_update->path, update->path);
+
+ /*
+ * The entries are sorted, so when we find we're no
+ * longer in the same directory, we need to abandon
+ * the old tree (steps up) and dive down to the next
+ * one.
+ */
+ steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]);
+
+ for (j = 0; j < steps_up; j++) {
+ tree_stack_entry *current, *popped = git_array_pop(stack);
+ assert(popped);
+
+ current = git_array_last(stack);
+ assert(current);
+
+ if ((error = create_popped_tree(current, popped, &component)) < 0)
+ goto cleanup;
+ }
+
+ /* Now that we've created the trees we popped from the stack, let's go back down */
+ path = &update->path[common_prefix];
+ while (next_component(&component, path)) {
+ tree_stack_entry *last, *new_entry;
+ const git_tree_entry *entry;
+
+ last = git_array_last(stack);
+ entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL;
+ if (!entry)
+ entry = treebuilder_get(last->bld, component.ptr);
+
+ if (entry && git_tree_entry_type(entry) != GIT_OBJ_TREE) {
+ giterr_set(GITERR_TREE, "D/F conflict when updating tree");
+ error = -1;
+ goto cleanup;
+ }
+
+ new_entry = git_array_alloc(stack);
+ GITERR_CHECK_ALLOC(new_entry);
+ memset(new_entry, 0, sizeof(*new_entry));
+
+ new_entry->tree = NULL;
+ if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0)
+ goto cleanup;
+
+ if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0)
+ goto cleanup;
+
+ new_entry->name = git__strdup(component.ptr);
+ GITERR_CHECK_ALLOC(new_entry->name);
+
+ /* Get to the start of the next component */
+ path += component.size + 1;
+ }
+
+ /* After all that, we're finally at the place where we want to perform the update */
+ switch (update->action) {
+ case GIT_TREE_UPDATE_UPSERT:
+ {
+ /* Make sure we're replacing something of the same type */
+ tree_stack_entry *last = git_array_last(stack);
+ char *basename = git_path_basename(update->path);
+ const git_tree_entry *e = git_treebuilder_get(last->bld, basename);
+ if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) {
+ git__free(basename);
+ giterr_set(GITERR_TREE, "Cannot replace '%s' with '%s' at '%s'",
+ git_object_type2string(git_tree_entry_type(e)),
+ git_object_type2string(git_object__type_from_filemode(update->filemode)),
+ update->path);
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode);
+ git__free(basename);
+ break;
+ }
+ case GIT_TREE_UPDATE_REMOVE:
+ {
+ char *basename = git_path_basename(update->path);
+ error = git_treebuilder_remove(git_array_last(stack)->bld, basename);
+ git__free(basename);
+ break;
+ }
+ default:
+ giterr_set(GITERR_TREE, "unkown action for update");
+ error = -1;
+ goto cleanup;
+ }
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ /* We're done, go up the stack again and write out the tree */
+ {
+ tree_stack_entry *current = NULL, *popped = NULL;
+ while ((popped = git_array_pop(stack)) != NULL) {
+ current = git_array_last(stack);
+ /* We've reached the top, current is the root tree */
+ if (!current)
+ break;
+
+ if ((error = create_popped_tree(current, popped, &component)) < 0)
+ goto cleanup;
+ }
+
+ /* Write out the root tree */
+ git__free(popped->name);
+ git_tree_free(popped->tree);
+
+ error = git_treebuilder_write(out, popped->bld);
+ git_treebuilder_free(popped->bld);
+ if (error < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ {
+ tree_stack_entry *e;
+ while ((e = git_array_pop(stack)) != NULL) {
+ git_treebuilder_free(e->bld);
+ git_tree_free(e->tree);
+ git__free(e->name);
+ }
+ }
+
+ git_buf_free(&component);
+ git_array_clear(stack);
+ git_vector_free(&entries);
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_tree_h__
+#define INCLUDE_tree_h__
+
+#include "git2/tree.h"
+#include "repository.h"
+#include "odb.h"
+#include "vector.h"
+#include "strmap.h"
+#include "pool.h"
+
+struct git_tree_entry {
+ uint16_t attr;
+ uint16_t filename_len;
+ const git_oid *oid;
+ const char *filename;
+};
+
+struct git_tree {
+ git_object object;
+ git_odb_object *odb_obj;
+ git_array_t(git_tree_entry) entries;
+};
+
+struct git_treebuilder {
+ git_repository *repo;
+ git_strmap *map;
+};
+
+GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e)
+{
+ return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr));
+}
+
+extern int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2);
+
+void git_tree__free(void *tree);
+int git_tree__parse(void *tree, git_odb_object *obj);
+
+/**
+ * Lookup the first position in the tree with a given prefix.
+ *
+ * @param tree a previously loaded tree.
+ * @param prefix the beginning of a path to find in the tree.
+ * @return index of the first item at or after the given prefix.
+ */
+int git_tree__prefix_position(const git_tree *tree, const char *prefix);
+
+
+/**
+ * Write a tree to the given repository
+ */
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo);
+
+/**
+ * Obsolete mode kept for compatibility reasons
+ */
+#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+
+/**
+ * An array-of-pointers implementation of Python's Timsort
+ * Based on code by Christopher Swenson under the MIT license
+ *
+ * Copyright (c) 2010 Christopher Swenson
+ * Copyright (c) 2011 Vicent Marti
+ */
+
+#ifndef MAX
+# define MAX(x,y) (((x) > (y) ? (x) : (y)))
+#endif
+
+#ifndef MIN
+# define MIN(x,y) (((x) < (y) ? (x) : (y)))
+#endif
+
+static int binsearch(
+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ int l, c, r;
+ void *lx, *cx;
+
+ assert(size > 0);
+
+ l = 0;
+ r = (int)size - 1;
+ c = r >> 1;
+ lx = dst[l];
+
+ /* check for beginning conditions */
+ if (cmp(x, lx, payload) < 0)
+ return 0;
+
+ else if (cmp(x, lx, payload) == 0) {
+ int i = 1;
+ while (cmp(x, dst[i], payload) == 0)
+ i++;
+ return i;
+ }
+
+ /* guaranteed not to be >= rx */
+ cx = dst[c];
+ while (1) {
+ const int val = cmp(x, cx, payload);
+ if (val < 0) {
+ if (c - l <= 1) return c;
+ r = c;
+ } else if (val > 0) {
+ if (r - c <= 1) return c + 1;
+ l = c;
+ lx = cx;
+ } else {
+ do {
+ cx = dst[++c];
+ } while (cmp(x, cx, payload) == 0);
+ return c;
+ }
+ c = l + ((r - l) >> 1);
+ cx = dst[c];
+ }
+}
+
+/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
+static void bisort(
+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ size_t i;
+ void *x;
+ int location;
+
+ for (i = start; i < size; i++) {
+ int j;
+ /* If this entry is already correct, just move along */
+ if (cmp(dst[i - 1], dst[i], payload) <= 0)
+ continue;
+
+ /* Else we need to find the right place, shift everything over, and squeeze in */
+ x = dst[i];
+ location = binsearch(dst, x, i, cmp, payload);
+ for (j = (int)i - 1; j >= location; j--) {
+ dst[j + 1] = dst[j];
+ }
+ dst[location] = x;
+ }
+}
+
+
+/* timsort implementation, based on timsort.txt */
+struct tsort_run {
+ ssize_t start;
+ ssize_t length;
+};
+
+struct tsort_store {
+ size_t alloc;
+ git__sort_r_cmp cmp;
+ void *payload;
+ void **storage;
+};
+
+static void reverse_elements(void **dst, ssize_t start, ssize_t end)
+{
+ while (start < end) {
+ void *tmp = dst[start];
+ dst[start] = dst[end];
+ dst[end] = tmp;
+
+ start++;
+ end--;
+ }
+}
+
+static ssize_t count_run(
+ void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
+{
+ ssize_t curr = start + 2;
+
+ if (size - start == 1)
+ return 1;
+
+ if (start >= size - 2) {
+ if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) {
+ void *tmp = dst[size - 1];
+ dst[size - 1] = dst[size - 2];
+ dst[size - 2] = tmp;
+ }
+
+ return 2;
+ }
+
+ if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0)
+ curr++;
+
+ return curr - start;
+ } else {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) > 0)
+ curr++;
+
+ /* reverse in-place */
+ reverse_elements(dst, start, curr - 1);
+ return curr - start;
+ }
+}
+
+static size_t compute_minrun(size_t n)
+{
+ int r = 0;
+ while (n >= 64) {
+ r |= n & 1;
+ n >>= 1;
+ }
+ return n + r;
+}
+
+static int check_invariant(struct tsort_run *stack, ssize_t stack_curr)
+{
+ if (stack_curr < 2)
+ return 1;
+
+ else if (stack_curr == 2) {
+ const ssize_t A = stack[stack_curr - 2].length;
+ const ssize_t B = stack[stack_curr - 1].length;
+ return (A > B);
+ } else {
+ const ssize_t A = stack[stack_curr - 3].length;
+ const ssize_t B = stack[stack_curr - 2].length;
+ const ssize_t C = stack[stack_curr - 1].length;
+ return !((A <= B + C) || (B <= C));
+ }
+}
+
+static int resize(struct tsort_store *store, size_t new_size)
+{
+ if (store->alloc < new_size) {
+ void **tempstore;
+
+ tempstore = git__reallocarray(store->storage, new_size, sizeof(void *));
+
+ /**
+ * Do not propagate on OOM; this will abort the sort and
+ * leave the array unsorted, but no error code will be
+ * raised
+ */
+ if (tempstore == NULL)
+ return -1;
+
+ store->storage = tempstore;
+ store->alloc = new_size;
+ }
+
+ return 0;
+}
+
+static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store)
+{
+ const ssize_t A = stack[stack_curr - 2].length;
+ const ssize_t B = stack[stack_curr - 1].length;
+ const ssize_t curr = stack[stack_curr - 2].start;
+
+ void **storage;
+ ssize_t i, j, k;
+
+ if (resize(store, MIN(A, B)) < 0)
+ return;
+
+ storage = store->storage;
+
+ /* left merge */
+ if (A < B) {
+ memcpy(storage, &dst[curr], A * sizeof(void *));
+ i = 0;
+ j = curr + A;
+
+ for (k = curr; k < curr + A + B; k++) {
+ if ((i < A) && (j < curr + A + B)) {
+ if (store->cmp(storage[i], dst[j], store->payload) <= 0)
+ dst[k] = storage[i++];
+ else
+ dst[k] = dst[j++];
+ } else if (i < A) {
+ dst[k] = storage[i++];
+ } else
+ dst[k] = dst[j++];
+ }
+ } else {
+ memcpy(storage, &dst[curr + A], B * sizeof(void *));
+ i = B - 1;
+ j = curr + A - 1;
+
+ for (k = curr + A + B - 1; k >= curr; k--) {
+ if ((i >= 0) && (j >= curr)) {
+ if (store->cmp(dst[j], storage[i], store->payload) > 0)
+ dst[k] = dst[j--];
+ else
+ dst[k] = storage[i--];
+ } else if (i >= 0)
+ dst[k] = storage[i--];
+ else
+ dst[k] = dst[j--];
+ }
+ }
+}
+
+static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size)
+{
+ ssize_t A, B, C;
+
+ while (1) {
+ /* if the stack only has one thing on it, we are done with the collapse */
+ if (stack_curr <= 1)
+ break;
+
+ /* if this is the last merge, just do it */
+ if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) {
+ merge(dst, stack, stack_curr, store);
+ stack[0].length += stack[1].length;
+ stack_curr--;
+ break;
+ }
+
+ /* check if the invariant is off for a stack of 2 elements */
+ else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) {
+ merge(dst, stack, stack_curr, store);
+ stack[0].length += stack[1].length;
+ stack_curr--;
+ break;
+ }
+ else if (stack_curr == 2)
+ break;
+
+ A = stack[stack_curr - 3].length;
+ B = stack[stack_curr - 2].length;
+ C = stack[stack_curr - 1].length;
+
+ /* check first invariant */
+ if (A <= B + C) {
+ if (A < C) {
+ merge(dst, stack, stack_curr - 1, store);
+ stack[stack_curr - 3].length += stack[stack_curr - 2].length;
+ stack[stack_curr - 2] = stack[stack_curr - 1];
+ stack_curr--;
+ } else {
+ merge(dst, stack, stack_curr, store);
+ stack[stack_curr - 2].length += stack[stack_curr - 1].length;
+ stack_curr--;
+ }
+ } else if (B <= C) {
+ merge(dst, stack, stack_curr, store);
+ stack[stack_curr - 2].length += stack[stack_curr - 1].length;
+ stack_curr--;
+ } else
+ break;
+ }
+
+ return stack_curr;
+}
+
+#define PUSH_NEXT() do {\
+ len = count_run(dst, curr, size, store);\
+ run = minrun;\
+ if (run < minrun) run = minrun;\
+ if (run > (ssize_t)size - curr) run = size - curr;\
+ if (run > len) {\
+ bisort(&dst[curr], len, run, cmp, payload);\
+ len = run;\
+ }\
+ run_stack[stack_curr].start = curr;\
+ run_stack[stack_curr++].length = len;\
+ curr += len;\
+ if (curr == (ssize_t)size) {\
+ /* finish up */ \
+ while (stack_curr > 1) { \
+ merge(dst, run_stack, stack_curr, store); \
+ run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \
+ stack_curr--; \
+ } \
+ if (store->storage != NULL) {\
+ git__free(store->storage);\
+ store->storage = NULL;\
+ }\
+ return;\
+ }\
+}\
+while (0)
+
+void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ struct tsort_store _store, *store = &_store;
+ struct tsort_run run_stack[128];
+
+ ssize_t stack_curr = 0;
+ ssize_t len, run;
+ ssize_t curr = 0;
+ ssize_t minrun;
+
+ if (size < 64) {
+ bisort(dst, 1, size, cmp, payload);
+ return;
+ }
+
+ /* compute the minimum run length */
+ minrun = (ssize_t)compute_minrun(size);
+
+ /* temporary storage for merges */
+ store->alloc = 0;
+ store->storage = NULL;
+ store->cmp = cmp;
+ store->payload = payload;
+
+ PUSH_NEXT();
+ PUSH_NEXT();
+ PUSH_NEXT();
+
+ while (1) {
+ if (!check_invariant(run_stack, stack_curr)) {
+ stack_curr = collapse(dst, run_stack, stack_curr, store, size);
+ continue;
+ }
+
+ PUSH_NEXT();
+ }
+}
+
+static int tsort_r_cmp(const void *a, const void *b, void *payload)
+{
+ return ((git__tsort_cmp)payload)(a, b);
+}
+
+void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)
+{
+ git__tsort_r(dst, size, tsort_r_cmp, cmp);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include <git2/common.h>
+
+#if !defined(GIT_WIN32) && !defined(NO_MMAP)
+
+#include "map.h"
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+
+int git__page_size(size_t *page_size)
+{
+ long sc_page_size = sysconf(_SC_PAGE_SIZE);
+ if (sc_page_size < 0) {
+ giterr_set(GITERR_OS, "can't determine system page size");
+ return -1;
+ }
+ *page_size = (size_t) sc_page_size;
+ return 0;
+}
+
+int git__mmap_alignment(size_t *alignment)
+{
+ return git__page_size(alignment);
+}
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
+{
+ int mprot = PROT_READ;
+ int mflag = 0;
+
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+
+ if (prot & GIT_PROT_WRITE)
+ mprot |= PROT_WRITE;
+
+ if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)
+ mflag = MAP_SHARED;
+ else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE)
+ mflag = MAP_PRIVATE;
+ else
+ mflag = MAP_SHARED;
+
+ out->data = mmap(NULL, len, mprot, mflag, fd, offset);
+
+ if (!out->data || out->data == MAP_FAILED) {
+ giterr_set(GITERR_OS, "Failed to mmap. Could not write data");
+ return -1;
+ }
+
+ out->len = len;
+
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ assert(map != NULL);
+ munmap(map->data, map->len);
+
+ return 0;
+}
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_posix__unix_h__
+#define INCLUDE_posix__unix_h__
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+typedef int GIT_SOCKET;
+#define INVALID_SOCKET -1
+
+#define p_lseek(f,n,w) lseek(f, n, w)
+#define p_fstat(f,b) fstat(f, b)
+#define p_lstat(p,b) lstat(p,b)
+#define p_stat(p,b) stat(p, b)
+
+#if defined(GIT_USE_STAT_MTIMESPEC)
+# define st_atime_nsec st_atimespec.tv_nsec
+# define st_mtime_nsec st_mtimespec.tv_nsec
+# define st_ctime_nsec st_ctimespec.tv_nsec
+#elif defined(GIT_USE_STAT_MTIM)
+# define st_atime_nsec st_atim.tv_nsec
+# define st_mtime_nsec st_mtim.tv_nsec
+# define st_ctime_nsec st_ctim.tv_nsec
+#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NEC)
+# error GIT_USE_NSEC defined but unknown struct stat nanosecond type
+#endif
+
+#define p_utimes(f, t) utimes(f, t)
+
+#define p_readlink(a, b, c) readlink(a, b, c)
+#define p_symlink(o,n) symlink(o, n)
+#define p_link(o,n) link(o, n)
+#define p_unlink(p) unlink(p)
+#define p_mkdir(p,m) mkdir(p, m)
+#define p_fsync(fd) fsync(fd)
+extern char *p_realpath(const char *, char *);
+
+#define p_recv(s,b,l,f) recv(s,b,l,f)
+#define p_send(s,b,l,f) send(s,b,l,f)
+#define p_inet_pton(a, b, c) inet_pton(a, b, c)
+
+#define p_strcasecmp(s1, s2) strcasecmp(s1, s2)
+#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c)
+#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
+#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
+#define p_mkstemp(p) mkstemp(p)
+#define p_chdir(p) chdir(p)
+#define p_chmod(p,m) chmod(p, m)
+#define p_rmdir(p) rmdir(p)
+#define p_access(p,m) access(p,m)
+#define p_ftruncate(fd, sz) ftruncate(fd, sz)
+
+/* see win32/posix.h for explanation about why this exists */
+#define p_lstat_posixly(p,b) lstat(p,b)
+
+#define p_localtime_r(c, r) localtime_r(c, r)
+#define p_gmtime_r(c, r) gmtime_r(c, r)
+
+#define p_timeval timeval
+
+#ifdef HAVE_FUTIMENS
+GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2])
+{
+ struct timespec s[2];
+ s[0].tv_sec = t[0].tv_sec;
+ s[0].tv_nsec = t[0].tv_usec * 1000;
+ s[1].tv_sec = t[1].tv_sec;
+ s[1].tv_nsec = t[1].tv_usec * 1000;
+ return futimens(f, s);
+}
+#else
+# define p_futimes futimes
+#endif
+
+#ifdef HAVE_REGCOMP_L
+#include <xlocale.h>
+GIT_INLINE(int) p_regcomp(regex_t *preg, const char *pattern, int cflags)
+{
+ return regcomp_l(preg, pattern, cflags, (locale_t) 0);
+}
+#else
+# define p_regcomp regcomp
+#endif
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_unix_pthread_h__
+#define INCLUDE_unix_pthread_h__
+
+typedef struct {
+ pthread_t thread;
+} git_thread;
+
+#define git_threads_init() (void)0
+#define git_thread_create(git_thread_ptr, start_routine, arg) \
+ pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ pthread_join((git_thread_ptr)->thread, status)
+#define git_thread_currentid() ((size_t)(pthread_self()))
+#define git_thread_exit(retval) pthread_exit(retval)
+
+/* Git Mutex */
+#define git_mutex pthread_mutex_t
+#define git_mutex_init(a) pthread_mutex_init(a, NULL)
+#define git_mutex_lock(a) pthread_mutex_lock(a)
+#define git_mutex_unlock(a) pthread_mutex_unlock(a)
+#define git_mutex_free(a) pthread_mutex_destroy(a)
+
+/* Git condition vars */
+#define git_cond pthread_cond_t
+#define git_cond_init(c) pthread_cond_init(c, NULL)
+#define git_cond_free(c) pthread_cond_destroy(c)
+#define git_cond_wait(c, l) pthread_cond_wait(c, l)
+#define git_cond_signal(c) pthread_cond_signal(c)
+#define git_cond_broadcast(c) pthread_cond_broadcast(c)
+
+/* Pthread (-ish) rwlock
+ *
+ * This differs from normal pthreads rwlocks in two ways:
+ * 1. Separate APIs for releasing read locks and write locks (as
+ * opposed to the pure POSIX API which only has one unlock fn)
+ * 2. You should not use recursive read locks (i.e. grabbing a read
+ * lock in a thread that already holds a read lock) because the
+ * Windows implementation doesn't support it
+ */
+#define git_rwlock pthread_rwlock_t
+#define git_rwlock_init(a) pthread_rwlock_init(a, NULL)
+#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a)
+#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a)
+#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a)
+#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a)
+#define git_rwlock_free(a) pthread_rwlock_destroy(a)
+#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER
+
+#endif /* INCLUDE_unix_pthread_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include <git2/common.h>
+
+#ifndef GIT_WIN32
+
+#include <limits.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+char *p_realpath(const char *pathname, char *resolved)
+{
+ char *ret;
+ if ((ret = realpath(pathname, resolved)) == NULL)
+ return NULL;
+
+#ifdef __OpenBSD__
+ /* The OpenBSD realpath function behaves differently,
+ * figure out if the file exists */
+ if (access(ret, F_OK) < 0)
+ ret = NULL;
+#endif
+ return ret;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_userdiff_h__
+#define INCLUDE_userdiff_h__
+
+/*
+ * This file isolates the built in diff driver function name patterns.
+ * Most of these patterns are taken from Git (with permission from the
+ * original authors for relicensing to libgit2).
+ */
+
+typedef struct {
+ const char *name;
+ const char *fns;
+ const char *words;
+ int flags;
+} git_diff_driver_definition;
+
+#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+"
+
+/*
+ * These builtin driver definition macros have same signature as in core
+ * git userdiff.c so that the data can be extracted verbatim
+ */
+#define PATTERNS(NAME, FN_PATS, WORD_PAT) \
+ { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, 0 }
+#define IPATTERN(NAME, FN_PATS, WORD_PAT) \
+ { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, REG_ICASE }
+
+/*
+ * The table of diff driver patterns
+ *
+ * Function name patterns are a list of newline separated patterns that
+ * match a function declaration (i.e. the line you want in the hunk header),
+ * or a negative pattern prefixed with a '!' to reject a pattern (such as
+ * rejecting goto labels in C code).
+ *
+ * Word boundary patterns are just a simple pattern that will be OR'ed with
+ * the default value above (i.e. whitespace or non-ASCII characters).
+ */
+static git_diff_driver_definition builtin_defs[] = {
+
+IPATTERN("ada",
+ "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n"
+ "!^[ \t]*with[ \t].*$\n"
+ "^[ \t]*((procedure|function)[ \t]+.*)$\n"
+ "^[ \t]*((package|protected|task)[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z][a-zA-Z0-9_]*"
+ "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?"
+ "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"),
+
+IPATTERN("fortran",
+ "!^([C*]|[ \t]*!)\n"
+ "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n"
+ "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA"
+ "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$",
+ /* -- */
+ "[a-zA-Z][a-zA-Z0-9_]*"
+ "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\."
+ /* numbers and format statements like 2E14.4, or ES12.6, 9X.
+ * Don't worry about format statements without leading digits since
+ * they would have been matched above as a variable anyway. */
+ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
+ "|//|\\*\\*|::|[/<>=]="),
+
+PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
+ "[^<>= \t]+"),
+
+PATTERNS("java",
+ "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
+ "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]="
+ "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
+
+PATTERNS("matlab",
+ "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$",
+ "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"),
+
+PATTERNS("objc",
+ /* Negate C statements that can look like functions */
+ "!^[ \t]*(do|for|if|else|return|switch|while)\n"
+ /* Objective-C methods */
+ "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n"
+ /* C functions */
+ "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n"
+ /* Objective-C class/protocol definitions */
+ "^(@(implementation|interface|protocol)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("pascal",
+ "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|"
+ "implementation|initialization|finalization)[ \t]*.*)$"
+ "\n"
+ "^(.*=[ \t]*(class|record).*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+ "|<>|<=|>=|:=|\\.\\."),
+
+PATTERNS("perl",
+ "^package .*\n"
+ "^sub [[:alnum:]_':]+[ \t]*"
+ "(\\([^)]*\\)[ \t]*)?" /* prototype */
+ /*
+ * Attributes. A regex can't count nested parentheses,
+ * so just slurp up whatever we see, taking care not
+ * to accept lines like "sub foo; # defined elsewhere".
+ *
+ * An attribute could contain a semicolon, but at that
+ * point it seems reasonable enough to give up.
+ */
+ "(:[^;#]*)?"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n" /* comment */
+ "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n"
+ "^=head[0-9] .*", /* POD */
+ /* -- */
+ "[[:alpha:]_'][[:alnum:]_']*"
+ "|0[xb]?[0-9a-fA-F_]*"
+ /* taking care not to interpret 3..5 as (3.)(.5) */
+ "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?"
+ "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::"
+ "|&&=|\\|\\|=|//=|\\*\\*="
+ "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?"
+ "|[-+*/%.^&<>=!|]="
+ "|=~|!~"
+ "|<<|<>|<=>|>>"),
+
+PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"),
+
+PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+ /* -- */
+ "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
+ "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"),
+
+PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+ "[={}\"]|[^={}\" \t]+"),
+
+PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
+
+PATTERNS("cpp",
+ /* Jump targets or access declarations */
+ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n"
+ /* functions/methods, variables, and compounds at top level */
+ "^((::[[:space:]]*)?[A-Za-z_].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"),
+
+PATTERNS("csharp",
+ /* Keywords */
+ "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
+ /* Methods and constructors */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+ /* Properties */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+ /* Type definitions */
+ "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
+ /* Namespace */
+ "^[ \t]*(namespace[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("php",
+ "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("javascript",
+ "([a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z0-9_$]+)*[ \t]*=[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n"
+ "([a-zA-Z_$][a-zA-Z0-9_$]*[ \t]*:[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n"
+ "[^a-zA-Z0-9_\\$](function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+};
+
+#undef IPATTERN
+#undef PATTERNS
+#undef WORD_DEFAULT
+
+#endif
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include <git2.h>
+#include "common.h"
+#include <stdio.h>
+#include <ctype.h>
+#include "posix.h"
+
+#ifdef GIT_WIN32
+# include "win32/w32_buffer.h"
+#endif
+
+#ifdef _MSC_VER
+# include <Shlwapi.h>
+#endif
+
+void git_strarray_free(git_strarray *array)
+{
+ size_t i;
+
+ if (array == NULL)
+ return;
+
+ for (i = 0; i < array->count; ++i)
+ git__free(array->strings[i]);
+
+ git__free(array->strings);
+
+ memset(array, 0, sizeof(*array));
+}
+
+int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
+{
+ size_t i;
+
+ assert(tgt && src);
+
+ memset(tgt, 0, sizeof(*tgt));
+
+ if (!src->count)
+ return 0;
+
+ tgt->strings = git__calloc(src->count, sizeof(char *));
+ GITERR_CHECK_ALLOC(tgt->strings);
+
+ for (i = 0; i < src->count; ++i) {
+ if (!src->strings[i])
+ continue;
+
+ tgt->strings[tgt->count] = git__strdup(src->strings[i]);
+ if (!tgt->strings[tgt->count]) {
+ git_strarray_free(tgt);
+ memset(tgt, 0, sizeof(*tgt));
+ return -1;
+ }
+
+ tgt->count++;
+ }
+
+ return 0;
+}
+
+int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base)
+{
+
+ return git__strntol64(result, nptr, (size_t)-1, endptr, base);
+}
+
+int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
+ const char *p;
+ int64_t n, nn;
+ int c, ovfl, v, neg, ndig;
+
+ p = nptr;
+ neg = 0;
+ n = 0;
+ ndig = 0;
+ ovfl = 0;
+
+ /*
+ * White space
+ */
+ while (git__isspace(*p))
+ p++;
+
+ /*
+ * Sign
+ */
+ if (*p == '-' || *p == '+')
+ if (*p++ == '-')
+ neg = 1;
+
+ /*
+ * Base
+ */
+ if (base == 0) {
+ if (*p != '0')
+ base = 10;
+ else {
+ base = 8;
+ if (p[1] == 'x' || p[1] == 'X') {
+ p += 2;
+ base = 16;
+ }
+ }
+ } else if (base == 16 && *p == '0') {
+ if (p[1] == 'x' || p[1] == 'X')
+ p += 2;
+ } else if (base < 0 || 36 < base)
+ goto Return;
+
+ /*
+ * Non-empty sequence of digits
+ */
+ for (; nptr_len > 0; p++,ndig++,nptr_len--) {
+ c = *p;
+ v = base;
+ if ('0'<=c && c<='9')
+ v = c - '0';
+ else if ('a'<=c && c<='z')
+ v = c - 'a' + 10;
+ else if ('A'<=c && c<='Z')
+ v = c - 'A' + 10;
+ if (v >= base)
+ break;
+ nn = n * base + (neg ? -v : v);
+ if ((!neg && nn < n) || (neg && nn > n))
+ ovfl = 1;
+ n = nn;
+ }
+
+Return:
+ if (ndig == 0) {
+ giterr_set(GITERR_INVALID, "Failed to convert string to long. Not a number");
+ return -1;
+ }
+
+ if (endptr)
+ *endptr = p;
+
+ if (ovfl) {
+ giterr_set(GITERR_INVALID, "Failed to convert string to long. Overflow error");
+ return -1;
+ }
+
+ *result = n;
+ return 0;
+}
+
+int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base)
+{
+
+ return git__strntol32(result, nptr, (size_t)-1, endptr, base);
+}
+
+int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
+ int error;
+ int32_t tmp_int;
+ int64_t tmp_long;
+
+ if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0)
+ return error;
+
+ tmp_int = tmp_long & 0xFFFFFFFF;
+ if (tmp_int != tmp_long) {
+ giterr_set(GITERR_INVALID, "Failed to convert. '%s' is too large", nptr);
+ return -1;
+ }
+
+ *result = tmp_int;
+
+ return error;
+}
+
+int git__strcmp(const char *a, const char *b)
+{
+ while (*a && *b && *a == *b)
+ ++a, ++b;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strcasecmp(const char *a, const char *b)
+{
+ while (*a && *b && git__tolower(*a) == git__tolower(*b))
+ ++a, ++b;
+ return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b));
+}
+
+int git__strcasesort_cmp(const char *a, const char *b)
+{
+ int cmp = 0;
+
+ while (*a && *b) {
+ if (*a != *b) {
+ if (git__tolower(*a) != git__tolower(*b))
+ break;
+ /* use case in sort order even if not in equivalence */
+ if (!cmp)
+ cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b);
+ }
+
+ ++a, ++b;
+ }
+
+ if (*a || *b)
+ return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b);
+
+ return cmp;
+}
+
+int git__strncmp(const char *a, const char *b, size_t sz)
+{
+ while (sz && *a && *b && *a == *b)
+ --sz, ++a, ++b;
+ if (!sz)
+ return 0;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strncasecmp(const char *a, const char *b, size_t sz)
+{
+ int al, bl;
+
+ do {
+ al = (unsigned char)git__tolower(*a);
+ bl = (unsigned char)git__tolower(*b);
+ ++a, ++b;
+ } while (--sz && al && al == bl);
+
+ return al - bl;
+}
+
+void git__strntolower(char *str, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; ++i) {
+ str[i] = (char)git__tolower(str[i]);
+ }
+}
+
+void git__strtolower(char *str)
+{
+ git__strntolower(str, strlen(str));
+}
+
+int git__prefixcmp(const char *str, const char *prefix)
+{
+ for (;;) {
+ unsigned char p = *(prefix++), s;
+ if (!p)
+ return 0;
+ if ((s = *(str++)) != p)
+ return s - p;
+ }
+}
+
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+ return strncasecmp(str, prefix, strlen(prefix));
+}
+
+int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
+{
+ int s, p;
+
+ while(str_n--) {
+ s = (unsigned char)git__tolower(*str++);
+ p = (unsigned char)git__tolower(*prefix++);
+
+ if (s != p)
+ return s - p;
+ }
+
+ return (0 - *prefix);
+}
+
+int git__suffixcmp(const char *str, const char *suffix)
+{
+ size_t a = strlen(str);
+ size_t b = strlen(suffix);
+ if (a < b)
+ return -1;
+ return strcmp(str + (a - b), suffix);
+}
+
+char *git__strtok(char **end, const char *sep)
+{
+ char *ptr = *end;
+
+ while (*ptr && strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ char *start = ptr;
+ *end = start + 1;
+
+ while (**end && !strchr(sep, **end))
+ ++*end;
+
+ if (**end) {
+ **end = '\0';
+ ++*end;
+ }
+
+ return start;
+ }
+
+ return NULL;
+}
+
+/* Similar to strtok, but does not collapse repeated tokens. */
+char *git__strsep(char **end, const char *sep)
+{
+ char *start = *end, *ptr = *end;
+
+ while (*ptr && !strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ *end = ptr + 1;
+ *ptr = '\0';
+
+ return start;
+ }
+
+ return NULL;
+}
+
+size_t git__linenlen(const char *buffer, size_t buffer_len)
+{
+ char *nl = memchr(buffer, '\n', buffer_len);
+ return nl ? (size_t)(nl - buffer) + 1 : buffer_len;
+}
+
+void git__hexdump(const char *buffer, size_t len)
+{
+ static const size_t LINE_WIDTH = 16;
+
+ size_t line_count, last_line, i, j;
+ const char *line;
+
+ line_count = (len / LINE_WIDTH);
+ last_line = (len % LINE_WIDTH);
+
+ for (i = 0; i < line_count; ++i) {
+ line = buffer + (i * LINE_WIDTH);
+ for (j = 0; j < LINE_WIDTH; ++j, ++line)
+ printf("%02X ", (unsigned char)*line & 0xFF);
+
+ printf("| ");
+
+ line = buffer + (i * LINE_WIDTH);
+ for (j = 0; j < LINE_WIDTH; ++j, ++line)
+ printf("%c", (*line >= 32 && *line <= 126) ? *line : '.');
+
+ printf("\n");
+ }
+
+ if (last_line > 0) {
+
+ line = buffer + (line_count * LINE_WIDTH);
+ for (j = 0; j < last_line; ++j, ++line)
+ printf("%02X ", (unsigned char)*line & 0xFF);
+
+ for (j = 0; j < (LINE_WIDTH - last_line); ++j)
+ printf(" ");
+
+ printf("| ");
+
+ line = buffer + (line_count * LINE_WIDTH);
+ for (j = 0; j < last_line; ++j, ++line)
+ printf("%c", (*line >= 32 && *line <= 126) ? *line : '.');
+
+ printf("\n");
+ }
+
+ printf("\n");
+}
+
+#ifdef GIT_LEGACY_HASH
+uint32_t git__hash(const void *key, int len, unsigned int seed)
+{
+ const uint32_t m = 0x5bd1e995;
+ const int r = 24;
+ uint32_t h = seed ^ len;
+
+ const unsigned char *data = (const unsigned char *)key;
+
+ while(len >= 4) {
+ uint32_t k = *(uint32_t *)data;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ data += 4;
+ len -= 4;
+ }
+
+ switch(len) {
+ case 3: h ^= data[2] << 16;
+ case 2: h ^= data[1] << 8;
+ case 1: h ^= data[0];
+ h *= m;
+ };
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ return h;
+}
+#else
+/*
+ Cross-platform version of Murmurhash3
+ http://code.google.com/p/smhasher/wiki/MurmurHash3
+ by Austin Appleby (aappleby@gmail.com)
+
+ This code is on the public domain.
+*/
+uint32_t git__hash(const void *key, int len, uint32_t seed)
+{
+
+#define MURMUR_BLOCK() {\
+ k1 *= c1; \
+ k1 = git__rotl(k1,11);\
+ k1 *= c2;\
+ h1 ^= k1;\
+ h1 = h1*3 + 0x52dce729;\
+ c1 = c1*5 + 0x7b7d159c;\
+ c2 = c2*5 + 0x6bce6396;\
+}
+
+ const uint8_t *data = (const uint8_t*)key;
+ const int nblocks = len / 4;
+
+ const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
+ const uint8_t *tail = (const uint8_t *)(data + nblocks * 4);
+
+ uint32_t h1 = 0x971e137b ^ seed;
+ uint32_t k1;
+
+ uint32_t c1 = 0x95543787;
+ uint32_t c2 = 0x2ad7eb25;
+
+ int i;
+
+ for (i = -nblocks; i; i++) {
+ k1 = blocks[i];
+ MURMUR_BLOCK();
+ }
+
+ k1 = 0;
+
+ switch(len & 3) {
+ case 3: k1 ^= tail[2] << 16;
+ case 2: k1 ^= tail[1] << 8;
+ case 1: k1 ^= tail[0];
+ MURMUR_BLOCK();
+ }
+
+ h1 ^= len;
+ h1 ^= h1 >> 16;
+ h1 *= 0x85ebca6b;
+ h1 ^= h1 >> 13;
+ h1 *= 0xc2b2ae35;
+ h1 ^= h1 >> 16;
+
+ return h1;
+}
+#endif
+
+/**
+ * A modified `bsearch` from the BSD glibc.
+ *
+ * Copyright (c) 1990 Regents of the University of California.
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. [rescinded 22 July 1999]
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *, const void *),
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare)(key, *part);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *, const void *, void *),
+ void *payload,
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare_r)(key, *part, payload);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+/**
+ * A strcmp wrapper
+ *
+ * We don't want direct pointers to the CRT on Windows, we may
+ * get stdcall conflicts.
+ */
+int git__strcmp_cb(const void *a, const void *b)
+{
+ return strcmp((const char *)a, (const char *)b);
+}
+
+int git__strcasecmp_cb(const void *a, const void *b)
+{
+ return strcasecmp((const char *)a, (const char *)b);
+}
+
+int git__parse_bool(int *out, const char *value)
+{
+ /* A missing value means true */
+ if (value == NULL ||
+ !strcasecmp(value, "true") ||
+ !strcasecmp(value, "yes") ||
+ !strcasecmp(value, "on")) {
+ *out = 1;
+ return 0;
+ }
+ if (!strcasecmp(value, "false") ||
+ !strcasecmp(value, "no") ||
+ !strcasecmp(value, "off") ||
+ value[0] == '\0') {
+ *out = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+size_t git__unescape(char *str)
+{
+ char *scan, *pos = str;
+
+ if (!str)
+ return 0;
+
+ for (scan = str; *scan; pos++, scan++) {
+ if (*scan == '\\' && *(scan + 1) != '\0')
+ scan++; /* skip '\' but include next char */
+ if (pos != scan)
+ *pos = *scan;
+ }
+
+ if (pos != scan) {
+ *pos = '\0';
+ }
+
+ return (pos - str);
+}
+
+#if defined(HAVE_QSORT_S) || (defined(HAVE_QSORT_R) && defined(BSD))
+typedef struct {
+ git__sort_r_cmp cmp;
+ void *payload;
+} git__qsort_r_glue;
+
+static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
+ void *payload, const void *a, const void *b)
+{
+ git__qsort_r_glue *glue = payload;
+ return glue->cmp(a, b, glue->payload);
+}
+#endif
+
+void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
+{
+#if defined(HAVE_QSORT_R) && defined(BSD)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);
+#elif defined(HAVE_QSORT_R) && defined(__GLIBC__)
+ qsort_r(els, nel, elsize, cmp, payload);
+#elif defined(HAVE_QSORT_S)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
+#else
+ git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
+#endif
+}
+
+void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload)
+{
+ uint8_t *base = els;
+ uint8_t *end = base + nel * elsize;
+ uint8_t *i, *j;
+ bool freeswap = !swapel;
+
+ if (freeswap)
+ swapel = git__malloc(elsize);
+
+ for (i = base + elsize; i < end; i += elsize)
+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {
+ memcpy(swapel, j, elsize);
+ memcpy(j, j - elsize, elsize);
+ memcpy(j - elsize, swapel, elsize);
+ }
+
+ if (freeswap)
+ git__free(swapel);
+}
+
+/*
+ * git__utf8_iterate is taken from the utf8proc project,
+ * http://www.public-software-group.org/utf8proc
+ *
+ * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the ""Software""),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+static const int8_t utf8proc_utf8class[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+int git__utf8_charlen(const uint8_t *str, int str_len)
+{
+ int length, i;
+
+ length = utf8proc_utf8class[str[0]];
+ if (!length)
+ return -1;
+
+ if (str_len >= 0 && length > str_len)
+ return -str_len;
+
+ for (i = 1; i < length; i++) {
+ if ((str[i] & 0xC0) != 0x80)
+ return -i;
+ }
+
+ return length;
+}
+
+int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst)
+{
+ int length;
+ int32_t uc = -1;
+
+ *dst = -1;
+ length = git__utf8_charlen(str, str_len);
+ if (length < 0)
+ return -1;
+
+ switch (length) {
+ case 1:
+ uc = str[0];
+ break;
+ case 2:
+ uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
+ if (uc < 0x80) uc = -1;
+ break;
+ case 3:
+ uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6)
+ + (str[2] & 0x3F);
+ if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
+ (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
+ break;
+ case 4:
+ uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
+ + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
+ if (uc < 0x10000 || uc >= 0x110000) uc = -1;
+ break;
+ }
+
+ if (uc < 0 || ((uc & 0xFFFF) >= 0xFFFE))
+ return -1;
+
+ *dst = uc;
+ return length;
+}
+
+double git_time_monotonic(void)
+{
+ return git__timer();
+}
+
+#ifdef GIT_WIN32
+int git__getenv(git_buf *out, const char *name)
+{
+ wchar_t *wide_name = NULL, *wide_value = NULL;
+ DWORD value_len;
+ int error = -1;
+
+ git_buf_clear(out);
+
+ if (git__utf8_to_16_alloc(&wide_name, name) < 0)
+ return -1;
+
+ if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) {
+ wide_value = git__malloc(value_len * sizeof(wchar_t));
+ GITERR_CHECK_ALLOC(wide_value);
+
+ value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len);
+ }
+
+ if (value_len)
+ error = git_buf_put_w(out, wide_value, value_len);
+ else if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)
+ error = GIT_ENOTFOUND;
+ else
+ giterr_set(GITERR_OS, "could not read environment variable '%s'", name);
+
+ git__free(wide_name);
+ git__free(wide_value);
+ return error;
+}
+#else
+int git__getenv(git_buf *out, const char *name)
+{
+ const char *val = getenv(name);
+
+ git_buf_clear(out);
+
+ if (!val)
+ return GIT_ENOTFOUND;
+
+ return git_buf_puts(out, val);
+}
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_util_h__
+#define INCLUDE_util_h__
+
+#include "git2/buffer.h"
+#include "buffer.h"
+
+#if defined(GIT_MSVC_CRTDBG)
+/* Enable MSVC CRTDBG memory leak reporting.
+ *
+ * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC
+ * documentation because all allocs/frees in libgit2 already go through
+ * the "git__" routines defined in this file. Simply using the normal
+ * reporting mechanism causes all leaks to be attributed to a routine
+ * here in util.h (ie, the actual call to calloc()) rather than the
+ * caller of git__calloc().
+ *
+ * Therefore, we declare a set of "git__crtdbg__" routines to replace
+ * the corresponding "git__" routines and re-define the "git__" symbols
+ * as macros. This allows us to get and report the file:line info of
+ * the real caller.
+ *
+ * We DO NOT replace the "git__free" routine because it needs to remain
+ * a function pointer because it is used as a function argument when
+ * setting up various structure "destructors".
+ *
+ * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes
+ * "free" to be remapped to "_free_dbg" and this causes problems for
+ * structures which define a field named "free".
+ *
+ * Finally, CRTDBG must be explicitly enabled and configured at program
+ * startup. See tests/main.c for an example.
+ */
+#include <stdlib.h>
+#include <crtdbg.h>
+#include "win32/w32_crtdbg_stacktrace.h"
+#endif
+
+#include "common.h"
+#include "strnlen.h"
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define bitsizeof(x) (CHAR_BIT * sizeof(x))
+#define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits))))
+#ifndef min
+# define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef max
+# define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define GIT_DATE_RFC2822_SZ 32
+
+/**
+ * Return the length of a constant string.
+ * We are aware that `strlen` performs the same task and is usually
+ * optimized away by the compiler, whilst being safer because it returns
+ * valid values when passed a pointer instead of a constant string; however
+ * this macro will transparently work with wide-char and single-char strings.
+ */
+#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
+
+#if defined(GIT_MSVC_CRTDBG)
+
+GIT_INLINE(void *) git__crtdbg__malloc(size_t len, const char *file, int line)
+{
+ void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(void *) git__crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line)
+{
+ void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(char *) git__crtdbg__strdup(const char *str, const char *file, int line)
+{
+ char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(char *) git__crtdbg__strndup(const char *str, size_t n, const char *file, int line)
+{
+ size_t length = 0, alloclength;
+ char *ptr;
+
+ length = p_strnlen(str, n);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) ||
+ !(ptr = git__crtdbg__malloc(alloclength, file, line)))
+ return NULL;
+
+ if (length)
+ memcpy(ptr, str, length);
+
+ ptr[length] = '\0';
+
+ return ptr;
+}
+
+GIT_INLINE(char *) git__crtdbg__substrdup(const char *start, size_t n, const char *file, int line)
+{
+ char *ptr;
+ size_t alloclen;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) ||
+ !(ptr = git__crtdbg__malloc(alloclen, file, line)))
+ return NULL;
+
+ memcpy(ptr, start, n);
+ ptr[n] = '\0';
+ return ptr;
+}
+
+GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file, int line)
+{
+ void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ if (!new_ptr) giterr_set_oom();
+ return new_ptr;
+}
+
+GIT_INLINE(void *) git__crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line)
+{
+ size_t newsize;
+
+ return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ?
+ NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+}
+
+GIT_INLINE(void *) git__crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line)
+{
+ return git__crtdbg__reallocarray(NULL, nelem, elsize, file, line);
+}
+
+#define git__malloc(len) git__crtdbg__malloc(len, __FILE__, __LINE__)
+#define git__calloc(nelem, elsize) git__crtdbg__calloc(nelem, elsize, __FILE__, __LINE__)
+#define git__strdup(str) git__crtdbg__strdup(str, __FILE__, __LINE__)
+#define git__strndup(str, n) git__crtdbg__strndup(str, n, __FILE__, __LINE__)
+#define git__substrdup(str, n) git__crtdbg__substrdup(str, n, __FILE__, __LINE__)
+#define git__realloc(ptr, size) git__crtdbg__realloc(ptr, size, __FILE__, __LINE__)
+#define git__reallocarray(ptr, nelem, elsize) git__crtdbg__reallocarray(ptr, nelem, elsize, __FILE__, __LINE__)
+#define git__mallocarray(nelem, elsize) git__crtdbg__mallocarray(nelem, elsize, __FILE__, __LINE__)
+
+#else
+
+/*
+ * Custom memory allocation wrappers
+ * that set error code and error message
+ * on allocation failure
+ */
+GIT_INLINE(void *) git__malloc(size_t len)
+{
+ void *ptr = malloc(len);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize)
+{
+ void *ptr = calloc(nelem, elsize);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(char *) git__strdup(const char *str)
+{
+ char *ptr = strdup(str);
+ if (!ptr) giterr_set_oom();
+ return ptr;
+}
+
+GIT_INLINE(char *) git__strndup(const char *str, size_t n)
+{
+ size_t length = 0, alloclength;
+ char *ptr;
+
+ length = p_strnlen(str, n);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) ||
+ !(ptr = git__malloc(alloclength)))
+ return NULL;
+
+ if (length)
+ memcpy(ptr, str, length);
+
+ ptr[length] = '\0';
+
+ return ptr;
+}
+
+/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */
+GIT_INLINE(char *) git__substrdup(const char *start, size_t n)
+{
+ char *ptr;
+ size_t alloclen;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) ||
+ !(ptr = git__malloc(alloclen)))
+ return NULL;
+
+ memcpy(ptr, start, n);
+ ptr[n] = '\0';
+ return ptr;
+}
+
+GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
+{
+ void *new_ptr = realloc(ptr, size);
+ if (!new_ptr) giterr_set_oom();
+ return new_ptr;
+}
+
+/**
+ * Similar to `git__realloc`, except that it is suitable for reallocing an
+ * array to a new number of elements of `nelem`, each of size `elsize`.
+ * The total size calculation is checked for overflow.
+ */
+GIT_INLINE(void *) git__reallocarray(void *ptr, size_t nelem, size_t elsize)
+{
+ size_t newsize;
+ return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ?
+ NULL : realloc(ptr, newsize);
+}
+
+/**
+ * Similar to `git__calloc`, except that it does not zero memory.
+ */
+GIT_INLINE(void *) git__mallocarray(size_t nelem, size_t elsize)
+{
+ return git__reallocarray(NULL, nelem, elsize);
+}
+
+#endif /* !MSVC_CTRDBG */
+
+GIT_INLINE(void) git__free(void *ptr)
+{
+ free(ptr);
+}
+
+#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \
+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))
+
+#define CASESELECT(IGNORE_CASE, ICASE, CASE) \
+ ((IGNORE_CASE) ? (ICASE) : (CASE))
+
+extern int git__prefixcmp(const char *str, const char *prefix);
+extern int git__prefixcmp_icase(const char *str, const char *prefix);
+extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
+extern int git__suffixcmp(const char *str, const char *suffix);
+
+GIT_INLINE(int) git__signum(int val)
+{
+ return ((val > 0) - (val < 0));
+}
+
+extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
+extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
+extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base);
+extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
+
+
+extern void git__hexdump(const char *buffer, size_t n);
+extern uint32_t git__hash(const void *key, int len, uint32_t seed);
+
+/* 32-bit cross-platform rotl */
+#ifdef _MSC_VER /* use built-in method in MSVC */
+# define git__rotl(v, s) (uint32_t)_rotl(v, s)
+#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */
+# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
+#endif
+
+extern char *git__strtok(char **end, const char *sep);
+extern char *git__strsep(char **end, const char *sep);
+
+extern void git__strntolower(char *str, size_t len);
+extern void git__strtolower(char *str);
+
+#ifdef GIT_WIN32
+GIT_INLINE(int) git__tolower(int c)
+{
+ return (c >= 'A' && c <= 'Z') ? (c + 32) : c;
+}
+#else
+# define git__tolower(a) tolower(a)
+#endif
+
+extern size_t git__linenlen(const char *buffer, size_t buffer_len);
+
+GIT_INLINE(const char *) git__next_line(const char *s)
+{
+ while (*s && *s != '\n') s++;
+ while (*s == '\n' || *s == '\r') s++;
+ return s;
+}
+
+GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return cp;
+ } while (--n != 0);
+ }
+
+ return NULL;
+}
+
+typedef int (*git__tsort_cmp)(const void *a, const void *b);
+
+extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);
+
+typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);
+
+extern void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);
+
+extern void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
+
+extern void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload);
+
+/**
+ * @param position If non-NULL, this will be set to the position where the
+ * element is or would be inserted if not found.
+ * @return 0 if found; GIT_ENOTFOUND if not found
+ */
+extern int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *key, const void *element),
+ size_t *position);
+
+extern int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *key, const void *element, void *payload),
+ void *payload,
+ size_t *position);
+
+extern int git__strcmp_cb(const void *a, const void *b);
+extern int git__strcasecmp_cb(const void *a, const void *b);
+
+extern int git__strcmp(const char *a, const char *b);
+extern int git__strcasecmp(const char *a, const char *b);
+extern int git__strncmp(const char *a, const char *b, size_t sz);
+extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+
+extern int git__strcasesort_cmp(const char *a, const char *b);
+
+#include "thread-utils.h"
+
+typedef struct {
+ git_atomic refcount;
+ void *owner;
+} git_refcount;
+
+typedef void (*git_refcount_freeptr)(void *r);
+
+#define GIT_REFCOUNT_INC(r) { \
+ git_atomic_inc(&((git_refcount *)(r))->refcount); \
+}
+
+#define GIT_REFCOUNT_DEC(_r, do_free) { \
+ git_refcount *r = (git_refcount *)(_r); \
+ int val = git_atomic_dec(&r->refcount); \
+ if (val <= 0 && r->owner == NULL) { do_free(_r); } \
+}
+
+#define GIT_REFCOUNT_OWN(r, o) { \
+ ((git_refcount *)(r))->owner = o; \
+}
+
+#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner)
+
+#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount)
+
+
+static signed char from_hex[] = {
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */
+};
+
+GIT_INLINE(int) git__fromhex(char h)
+{
+ return from_hex[(unsigned char) h];
+}
+
+GIT_INLINE(int) git__ishex(const char *str)
+{
+ unsigned i;
+ for (i=0; str[i] != '\0'; i++)
+ if (git__fromhex(str[i]) < 0)
+ return 0;
+ return 1;
+}
+
+GIT_INLINE(size_t) git__size_t_bitmask(size_t v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+
+ return v;
+}
+
+GIT_INLINE(size_t) git__size_t_powerof2(size_t v)
+{
+ return git__size_t_bitmask(v) + 1;
+}
+
+GIT_INLINE(bool) git__isupper(int c)
+{
+ return (c >= 'A' && c <= 'Z');
+}
+
+GIT_INLINE(bool) git__isalpha(int c)
+{
+ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+}
+
+GIT_INLINE(bool) git__isdigit(int c)
+{
+ return (c >= '0' && c <= '9');
+}
+
+GIT_INLINE(bool) git__isspace(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');
+}
+
+GIT_INLINE(bool) git__isspace_nonlf(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v');
+}
+
+GIT_INLINE(bool) git__iswildcard(int c)
+{
+ return (c == '*' || c == '?' || c == '[');
+}
+
+GIT_INLINE(bool) git__isxdigit(int c)
+{
+ return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+/*
+ * Parse a string value as a boolean, just like Core Git does.
+ *
+ * Valid values for true are: 'true', 'yes', 'on'
+ * Valid values for false are: 'false', 'no', 'off'
+ */
+extern int git__parse_bool(int *out, const char *value);
+
+/*
+ * Parse a string into a value as a git_time_t.
+ *
+ * Sample valid input:
+ * - "yesterday"
+ * - "July 17, 2003"
+ * - "2003-7-17 08:23"
+ */
+extern int git__date_parse(git_time_t *out, const char *date);
+
+/*
+ * Format a git_time as a RFC2822 string
+ *
+ * @param out buffer to store formatted date; a '\\0' terminator will automatically be added.
+ * @param len size of the buffer; should be atleast `GIT_DATE_RFC2822_SZ` in size;
+ * @param date the date to be formatted
+ * @return 0 if successful; -1 on error
+ */
+extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date);
+
+/*
+ * Unescapes a string in-place.
+ *
+ * Edge cases behavior:
+ * - "jackie\" -> "jacky\"
+ * - "chan\\" -> "chan\"
+ */
+extern size_t git__unescape(char *str);
+
+/*
+ * Iterate through an UTF-8 string, yielding one
+ * codepoint at a time.
+ *
+ * @param str current position in the string
+ * @param str_len size left in the string; -1 if the string is NULL-terminated
+ * @param dst pointer where to store the current codepoint
+ * @return length in bytes of the read codepoint; -1 if the codepoint was invalid
+ */
+extern int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst);
+
+/*
+ * Safely zero-out memory, making sure that the compiler
+ * doesn't optimize away the operation.
+ */
+GIT_INLINE(void) git__memzero(void *data, size_t size)
+{
+#ifdef _MSC_VER
+ SecureZeroMemory((PVOID)data, size);
+#else
+ volatile uint8_t *scan = (volatile uint8_t *)data;
+
+ while (size--)
+ *scan++ = 0x0;
+#endif
+}
+
+#ifdef GIT_WIN32
+
+GIT_INLINE(double) git__timer(void)
+{
+ /* We need the initial tick count to detect if the tick
+ * count has rolled over. */
+ static DWORD initial_tick_count = 0;
+
+ /* GetTickCount returns the number of milliseconds that have
+ * elapsed since the system was started. */
+ DWORD count = GetTickCount();
+
+ if(initial_tick_count == 0) {
+ initial_tick_count = count;
+ } else if (count < initial_tick_count) {
+ /* The tick count has rolled over - adjust for it. */
+ count = (0xFFFFFFFF - initial_tick_count) + count;
+ }
+
+ return (double) count / (double) 1000;
+}
+
+#elif __APPLE__
+
+#include <mach/mach_time.h>
+
+GIT_INLINE(double) git__timer(void)
+{
+ uint64_t time = mach_absolute_time();
+ static double scaling_factor = 0;
+
+ if (scaling_factor == 0) {
+ mach_timebase_info_data_t info;
+ (void)mach_timebase_info(&info);
+ scaling_factor = (double)info.numer / (double)info.denom;
+ }
+
+ return (double)time * scaling_factor / 1.0E9;
+}
+
+#elif defined(AMIGA)
+
+#include <proto/timer.h>
+
+GIT_INLINE(double) git__timer(void)
+{
+ struct TimeVal tv;
+ ITimer->GetUpTime(&tv);
+ return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6;
+}
+
+#else
+
+#include <sys/time.h>
+
+GIT_INLINE(double) git__timer(void)
+{
+ struct timespec tp;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
+ return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9;
+ } else {
+ /* Fall back to using gettimeofday */
+ struct timeval tv;
+ struct timezone tz;
+ gettimeofday(&tv, &tz);
+ return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6;
+ }
+}
+
+#endif
+
+extern int git__getenv(git_buf *out, const char *name);
+
+#endif /* INCLUDE_util_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "varint.h"
+
+uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len)
+{
+ const unsigned char *buf = bufp;
+ unsigned char c = *buf++;
+ uintmax_t val = c & 127;
+ while (c & 128) {
+ val += 1;
+ if (!val || MSB(val, 7)) {
+ /* This is not a valid varint_len, so it signals
+ the error */
+ *varint_len = 0;
+ return 0; /* overflow */
+ }
+ c = *buf++;
+ val = (val << 7) + (c & 127);
+ }
+ *varint_len = buf - bufp;
+ return val;
+}
+
+int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value)
+{
+ unsigned char varint[16];
+ unsigned pos = sizeof(varint) - 1;
+ varint[pos] = value & 127;
+ while (value >>= 7)
+ varint[--pos] = 128 | (--value & 127);
+ if (buf) {
+ if (bufsize < pos)
+ return -1;
+ memcpy(buf, varint + pos, sizeof(varint) - pos);
+ }
+ return sizeof(varint) - pos;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_varint_h__
+#define INCLUDE_varint_h__
+
+#include <stdint.h>
+
+extern int git_encode_varint(unsigned char *, size_t, uintmax_t);
+extern uintmax_t git_decode_varint(const unsigned char *, size_t *);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "vector.h"
+#include "integer.h"
+
+/* In elements, not bytes */
+#define MIN_ALLOCSIZE 8
+
+GIT_INLINE(size_t) compute_new_size(git_vector *v)
+{
+ size_t new_size = v->_alloc_size;
+
+ /* Use a resize factor of 1.5, which is quick to compute using integer
+ * instructions and less than the golden ratio (1.618...) */
+ if (new_size < MIN_ALLOCSIZE)
+ new_size = MIN_ALLOCSIZE;
+ else if (new_size <= (SIZE_MAX / 3) * 2)
+ new_size += new_size / 2;
+ else
+ new_size = SIZE_MAX;
+
+ return new_size;
+}
+
+GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size)
+{
+ void *new_contents;
+
+ new_contents = git__reallocarray(v->contents, new_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(new_contents);
+
+ v->_alloc_size = new_size;
+ v->contents = new_contents;
+
+ return 0;
+}
+
+int git_vector_size_hint(git_vector *v, size_t size_hint)
+{
+ if (v->_alloc_size >= size_hint)
+ return 0;
+ return resize_vector(v, size_hint);
+}
+
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)
+{
+ size_t bytes;
+
+ assert(v && src);
+
+ GITERR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *));
+
+ v->_alloc_size = src->length;
+ v->_cmp = cmp ? cmp : src->_cmp;
+ v->length = src->length;
+ v->flags = src->flags;
+ if (cmp != src->_cmp)
+ git_vector_set_sorted(v, 0);
+ v->contents = git__malloc(bytes);
+ GITERR_CHECK_ALLOC(v->contents);
+
+ memcpy(v->contents, src->contents, bytes);
+
+ return 0;
+}
+
+void git_vector_free(git_vector *v)
+{
+ assert(v);
+
+ git__free(v->contents);
+ v->contents = NULL;
+
+ v->length = 0;
+ v->_alloc_size = 0;
+}
+
+void git_vector_free_deep(git_vector *v)
+{
+ size_t i;
+
+ assert(v);
+
+ for (i = 0; i < v->length; ++i) {
+ git__free(v->contents[i]);
+ v->contents[i] = NULL;
+ }
+
+ git_vector_free(v);
+}
+
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp)
+{
+ assert(v);
+
+ v->_alloc_size = 0;
+ v->_cmp = cmp;
+ v->length = 0;
+ v->flags = GIT_VECTOR_SORTED;
+ v->contents = NULL;
+
+ return resize_vector(v, max(initial_size, MIN_ALLOCSIZE));
+}
+
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v)
+{
+ void **data = v->contents;
+
+ if (size)
+ *size = v->length;
+ if (asize)
+ *asize = v->_alloc_size;
+
+ v->_alloc_size = 0;
+ v->length = 0;
+ v->contents = NULL;
+
+ return data;
+}
+
+int git_vector_insert(git_vector *v, void *element)
+{
+ assert(v);
+
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
+
+ v->contents[v->length++] = element;
+
+ git_vector_set_sorted(v, v->length <= 1);
+
+ return 0;
+}
+
+int git_vector_insert_sorted(
+ git_vector *v, void *element, int (*on_dup)(void **old, void *new))
+{
+ int result;
+ size_t pos;
+
+ assert(v && v->_cmp);
+
+ if (!git_vector_is_sorted(v))
+ git_vector_sort(v);
+
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
+
+ /* If we find the element and have a duplicate handler callback,
+ * invoke it. If it returns non-zero, then cancel insert, otherwise
+ * proceed with normal insert.
+ */
+ if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) &&
+ on_dup && (result = on_dup(&v->contents[pos], element)) < 0)
+ return result;
+
+ /* shift elements to the right */
+ if (pos < v->length)
+ memmove(v->contents + pos + 1, v->contents + pos,
+ (v->length - pos) * sizeof(void *));
+
+ v->contents[pos] = element;
+ v->length++;
+
+ return 0;
+}
+
+void git_vector_sort(git_vector *v)
+{
+ assert(v);
+
+ if (git_vector_is_sorted(v) || !v->_cmp)
+ return;
+
+ if (v->length > 1)
+ git__tsort(v->contents, v->length, v->_cmp);
+ git_vector_set_sorted(v, 1);
+}
+
+int git_vector_bsearch2(
+ size_t *at_pos,
+ git_vector *v,
+ git_vector_cmp key_lookup,
+ const void *key)
+{
+ assert(v && key && key_lookup);
+
+ /* need comparison function to sort the vector */
+ if (!v->_cmp)
+ return -1;
+
+ git_vector_sort(v);
+
+ return git__bsearch(v->contents, v->length, key, key_lookup, at_pos);
+}
+
+int git_vector_search2(
+ size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key)
+{
+ size_t i;
+
+ assert(v && key && key_lookup);
+
+ for (i = 0; i < v->length; ++i) {
+ if (key_lookup(key, v->contents[i]) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+static int strict_comparison(const void *a, const void *b)
+{
+ return (a == b) ? 0 : -1;
+}
+
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry)
+{
+ return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry);
+}
+
+int git_vector_remove(git_vector *v, size_t idx)
+{
+ size_t shift_count;
+
+ assert(v);
+
+ if (idx >= v->length)
+ return GIT_ENOTFOUND;
+
+ shift_count = v->length - idx - 1;
+
+ if (shift_count)
+ memmove(&v->contents[idx], &v->contents[idx + 1],
+ shift_count * sizeof(void *));
+
+ v->length--;
+ return 0;
+}
+
+void git_vector_pop(git_vector *v)
+{
+ if (v->length > 0)
+ v->length--;
+}
+
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *))
+{
+ git_vector_cmp cmp;
+ size_t i, j;
+
+ if (v->length <= 1)
+ return;
+
+ git_vector_sort(v);
+ cmp = v->_cmp ? v->_cmp : strict_comparison;
+
+ for (i = 0, j = 1 ; j < v->length; ++j)
+ if (!cmp(v->contents[i], v->contents[j])) {
+ if (git_free_cb)
+ git_free_cb(v->contents[i]);
+
+ v->contents[i] = v->contents[j];
+ } else
+ v->contents[++i] = v->contents[j];
+
+ v->length -= j - i - 1;
+}
+
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload)
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; j < v->length; ++j) {
+ v->contents[i] = v->contents[j];
+
+ if (!match(v, i, payload))
+ i++;
+ }
+
+ v->length = i;
+}
+
+void git_vector_clear(git_vector *v)
+{
+ assert(v);
+ v->length = 0;
+ git_vector_set_sorted(v, 1);
+}
+
+void git_vector_swap(git_vector *a, git_vector *b)
+{
+ git_vector t;
+
+ assert(a && b);
+
+ if (a != b) {
+ memcpy(&t, a, sizeof(t));
+ memcpy(a, b, sizeof(t));
+ memcpy(b, &t, sizeof(t));
+ }
+}
+
+int git_vector_resize_to(git_vector *v, size_t new_length)
+{
+ if (new_length > v->_alloc_size &&
+ resize_vector(v, new_length) < 0)
+ return -1;
+
+ if (new_length > v->length)
+ memset(&v->contents[v->length], 0,
+ sizeof(void *) * (new_length - v->length));
+
+ v->length = new_length;
+
+ return 0;
+}
+
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len)
+{
+ size_t new_length;
+
+ assert(insert_len > 0 && idx <= v->length);
+
+ GITERR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len);
+
+ if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0)
+ return -1;
+
+ memmove(&v->contents[idx + insert_len], &v->contents[idx],
+ sizeof(void *) * (v->length - idx));
+ memset(&v->contents[idx], 0, sizeof(void *) * insert_len);
+
+ v->length = new_length;
+ return 0;
+}
+
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len)
+{
+ size_t new_length = v->length - remove_len;
+ size_t end_idx = 0;
+
+ assert(remove_len > 0);
+
+ if (git__add_sizet_overflow(&end_idx, idx, remove_len))
+ assert(0);
+
+ assert(end_idx <= v->length);
+
+ if (end_idx < v->length)
+ memmove(&v->contents[idx], &v->contents[end_idx],
+ sizeof(void *) * (v->length - end_idx));
+
+ memset(&v->contents[new_length], 0, sizeof(void *) * remove_len);
+
+ v->length = new_length;
+ return 0;
+}
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value)
+{
+ if (position + 1 > v->length) {
+ if (git_vector_resize_to(v, position + 1) < 0)
+ return -1;
+ }
+
+ if (old != NULL)
+ *old = v->contents[position];
+
+ v->contents[position] = value;
+
+ return 0;
+}
+
+int git_vector_verify_sorted(const git_vector *v)
+{
+ size_t i;
+
+ if (!git_vector_is_sorted(v))
+ return -1;
+
+ for (i = 1; i < v->length; ++i) {
+ if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+void git_vector_reverse(git_vector *v)
+{
+ size_t a, b;
+
+ a = 0;
+ b = v->length - 1;
+
+ while (a < b) {
+ void *tmp = v->contents[a];
+ v->contents[a] = v->contents[b];
+ v->contents[b] = tmp;
+ a++;
+ b--;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_vector_h__
+#define INCLUDE_vector_h__
+
+#include "common.h"
+
+typedef int (*git_vector_cmp)(const void *, const void *);
+
+enum {
+ GIT_VECTOR_SORTED = (1u << 0),
+ GIT_VECTOR_FLAG_MAX = (1u << 1),
+};
+
+typedef struct git_vector {
+ size_t _alloc_size;
+ git_vector_cmp _cmp;
+ void **contents;
+ size_t length;
+ uint32_t flags;
+} git_vector;
+
+#define GIT_VECTOR_INIT {0}
+
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp);
+void git_vector_free(git_vector *v);
+void git_vector_free_deep(git_vector *v); /* free each entry and self */
+void git_vector_clear(git_vector *v);
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp);
+void git_vector_swap(git_vector *a, git_vector *b);
+int git_vector_size_hint(git_vector *v, size_t size_hint);
+
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v);
+
+void git_vector_sort(git_vector *v);
+
+/** Linear search for matching entry using internal comparison function */
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry);
+
+/** Linear search for matching entry using explicit comparison function */
+int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key);
+
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
+int git_vector_bsearch2(
+ size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+
+/** Binary search for matching entry using internal comparison function */
+GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key)
+{
+ return git_vector_bsearch2(at_pos, v, v->_cmp, key);
+}
+
+GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position)
+{
+ return (position < v->length) ? v->contents[position] : NULL;
+}
+
+#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL)
+
+GIT_INLINE(size_t) git_vector_length(const git_vector *v)
+{
+ return v->length;
+}
+
+GIT_INLINE(void *) git_vector_last(const git_vector *v)
+{
+ return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
+}
+
+#define git_vector_foreach(v, iter, elem) \
+ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
+
+#define git_vector_rforeach(v, iter, elem) \
+ for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- )
+
+int git_vector_insert(git_vector *v, void *element);
+int git_vector_insert_sorted(git_vector *v, void *element,
+ int (*on_dup)(void **old, void *new));
+int git_vector_remove(git_vector *v, size_t idx);
+void git_vector_pop(git_vector *v);
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *));
+
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload);
+
+int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len);
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len);
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value);
+
+/** Check if vector is sorted */
+#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0)
+
+/** Directly set sorted state of vector */
+#define git_vector_set_sorted(V,S) do { \
+ (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \
+ ((V)->flags & ~GIT_VECTOR_SORTED); } while (0)
+
+/** Set the comparison function used for sorting the vector */
+GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp)
+{
+ if (cmp != v->_cmp) {
+ v->_cmp = cmp;
+ git_vector_set_sorted(v, 0);
+ }
+}
+
+/* Just use this in tests, not for realz. returns -1 if not sorted */
+int git_vector_verify_sorted(const git_vector *v);
+
+/**
+ * Reverse the vector in-place.
+ */
+void git_vector_reverse(git_vector *v);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#define GIT__WIN32_NO_WRAP_DIR
+#include "posix.h"
+
+git__DIR *git__opendir(const char *dir)
+{
+ git_win32_path filter_w;
+ git__DIR *new = NULL;
+ size_t dirlen, alloclen;
+
+ if (!dir || !git_win32__findfirstfile_filter(filter_w, dir))
+ return NULL;
+
+ dirlen = strlen(dir);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) ||
+ GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) ||
+ !(new = git__calloc(1, alloclen)))
+ return NULL;
+
+ memcpy(new->dir, dir, dirlen);
+
+ new->h = FindFirstFileW(filter_w, &new->f);
+
+ if (new->h == INVALID_HANDLE_VALUE) {
+ giterr_set(GITERR_OS, "Could not open directory '%s'", dir);
+ git__free(new);
+ return NULL;
+ }
+
+ new->first = 1;
+ return new;
+}
+
+int git__readdir_ext(
+ git__DIR *d,
+ struct git__dirent *entry,
+ struct git__dirent **result,
+ int *is_dir)
+{
+ if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE)
+ return -1;
+
+ *result = NULL;
+
+ if (d->first)
+ d->first = 0;
+ else if (!FindNextFileW(d->h, &d->f)) {
+ if (GetLastError() == ERROR_NO_MORE_FILES)
+ return 0;
+ giterr_set(GITERR_OS, "Could not read from directory '%s'", d->dir);
+ return -1;
+ }
+
+ /* Convert the path to UTF-8 */
+ if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0)
+ return -1;
+
+ entry->d_ino = 0;
+
+ *result = entry;
+
+ if (is_dir != NULL)
+ *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
+
+ return 0;
+}
+
+struct git__dirent *git__readdir(git__DIR *d)
+{
+ struct git__dirent *result;
+ if (git__readdir_ext(d, &d->entry, &result, NULL) < 0)
+ return NULL;
+ return result;
+}
+
+void git__rewinddir(git__DIR *d)
+{
+ git_win32_path filter_w;
+
+ if (!d)
+ return;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
+ d->h = INVALID_HANDLE_VALUE;
+ d->first = 0;
+ }
+
+ if (!git_win32__findfirstfile_filter(filter_w, d->dir))
+ return;
+
+ d->h = FindFirstFileW(filter_w, &d->f);
+
+ if (d->h == INVALID_HANDLE_VALUE)
+ giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir);
+ else
+ d->first = 1;
+}
+
+int git__closedir(git__DIR *d)
+{
+ if (!d)
+ return 0;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
+ d->h = INVALID_HANDLE_VALUE;
+ }
+
+ git__free(d);
+ return 0;
+}
+
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_dir_h__
+#define INCLUDE_dir_h__
+
+#include "common.h"
+#include "w32_util.h"
+
+struct git__dirent {
+ int d_ino;
+ git_win32_utf8_path d_name;
+};
+
+typedef struct {
+ HANDLE h;
+ WIN32_FIND_DATAW f;
+ struct git__dirent entry;
+ int first;
+ char dir[GIT_FLEX_ARRAY];
+} git__DIR;
+
+extern git__DIR *git__opendir(const char *);
+extern struct git__dirent *git__readdir(git__DIR *);
+extern int git__readdir_ext(
+ git__DIR *, struct git__dirent *, struct git__dirent **, int *);
+extern void git__rewinddir(git__DIR *);
+extern int git__closedir(git__DIR *);
+
+# ifndef GIT__WIN32_NO_WRAP_DIR
+# define dirent git__dirent
+# define DIR git__DIR
+# define opendir git__opendir
+# define readdir git__readdir
+# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL)
+# define rewinddir git__rewinddir
+# define closedir git__closedir
+# endif
+
+#endif /* INCLUDE_dir_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "error.h"
+#include "utf-conv.h"
+
+#ifdef GIT_WINHTTP
+# include <winhttp.h>
+#endif
+
+char *git_win32_get_error_message(DWORD error_code)
+{
+ LPWSTR lpMsgBuf = NULL;
+ HMODULE hModule = NULL;
+ char *utf8_msg = NULL;
+ DWORD dwFlags =
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ if (!error_code)
+ return NULL;
+
+#ifdef GIT_WINHTTP
+ /* Errors raised by WinHTTP are not in the system resource table */
+ if (error_code >= WINHTTP_ERROR_BASE &&
+ error_code <= WINHTTP_ERROR_LAST)
+ hModule = GetModuleHandleW(L"winhttp");
+#endif
+
+ GIT_UNUSED(hModule);
+
+ if (hModule)
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ else
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (FormatMessageW(dwFlags, hModule, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&lpMsgBuf, 0, NULL)) {
+ /* Convert the message to UTF-8. If this fails, we will
+ * return NULL, which is a condition expected by the caller */
+ if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0)
+ utf8_msg = NULL;
+
+ LocalFree(lpMsgBuf);
+ }
+
+ return utf8_msg;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_win32_error_h__
+#define INCLUDE_git_win32_error_h__
+
+extern char *git_win32_get_error_message(DWORD error_code);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "path.h"
+#include "findfile.h"
+
+#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+
+#ifndef _WIN64
+#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
+#else
+#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+#endif
+
+typedef struct {
+ git_win32_path path;
+ DWORD len;
+} _findfile_path;
+
+static int git_win32__expand_path(_findfile_path *dest, const wchar_t *src)
+{
+ dest->len = ExpandEnvironmentStringsW(src, dest->path, ARRAY_SIZE(dest->path));
+
+ if (!dest->len || dest->len > ARRAY_SIZE(dest->path))
+ return -1;
+
+ return 0;
+}
+
+static int win32_path_to_8(git_buf *dest, const wchar_t *src)
+{
+ git_win32_utf8_path utf8_path;
+
+ if (git_win32_path_to_utf8(utf8_path, src) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert path to UTF-8");
+ return -1;
+ }
+
+ /* Convert backslashes to forward slashes */
+ git_path_mkposix(utf8_path);
+
+ return git_buf_sets(dest, utf8_path);
+}
+
+static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
+{
+ wchar_t term, *base = path;
+
+ assert(path && buf && buflen);
+
+ term = (*path == L'"') ? *path++ : L';';
+
+ for (buflen--; *path && *path != term && buflen; buflen--)
+ *buf++ = *path++;
+
+ *buf = L'\0'; /* reserved a byte via initial subtract */
+
+ while (*path == term || *path == L';')
+ path++;
+
+ return (path != base) ? path : NULL;
+}
+
+static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir)
+{
+ wchar_t *env = _wgetenv(L"PATH"), lastch;
+ _findfile_path root;
+ size_t gitexe_len = wcslen(gitexe);
+
+ if (!env)
+ return -1;
+
+ while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {
+ root.len = (DWORD)wcslen(root.path);
+ lastch = root.path[root.len - 1];
+
+ /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
+ if (lastch != L'/' && lastch != L'\\') {
+ root.path[root.len++] = L'\\';
+ root.path[root.len] = L'\0';
+ }
+
+ if (root.len + gitexe_len >= MAX_PATH)
+ continue;
+ wcscpy(&root.path[root.len], gitexe);
+
+ if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
+ /* replace "bin\\" or "cmd\\" with subdir */
+ wcscpy(&root.path[root.len - 4], subdir);
+
+ win32_path_to_8(buf, root.path);
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+static int win32_find_git_in_registry(
+ git_buf *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir)
+{
+ HKEY hKey;
+ int error = GIT_ENOTFOUND;
+
+ assert(buf);
+
+ if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) {
+ DWORD dwType, cbData;
+ git_win32_path path;
+
+ /* Ensure that the buffer is big enough to have the suffix attached
+ * after we receive the result. */
+ cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t));
+
+ /* InstallLocation points to the root of the git directory */
+ if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) &&
+ dwType == REG_SZ) {
+
+ /* Append the suffix */
+ wcscat(path, subdir);
+
+ /* Convert to UTF-8, with forward slashes, and output the path
+ * to the provided buffer */
+ if (!win32_path_to_8(buf, path))
+ error = 0;
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return error;
+}
+
+static int win32_find_existing_dirs(
+ git_buf *out, const wchar_t *tmpl[])
+{
+ _findfile_path path16;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_clear(out);
+
+ for (; *tmpl != NULL; tmpl++) {
+ if (!git_win32__expand_path(&path16, *tmpl) &&
+ path16.path[0] != L'%' &&
+ !_waccess(path16.path, F_OK))
+ {
+ win32_path_to_8(&buf, path16.path);
+
+ if (buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+ }
+ }
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_system_dirs(git_buf *out, const wchar_t *subdir)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* directories where git.exe & git.cmd are found */
+ if (!win32_find_git_in_path(&buf, L"git.exe", subdir) && buf.size)
+ git_buf_set(out, buf.ptr, buf.size);
+ else
+ git_buf_clear(out);
+
+ if (!win32_find_git_in_path(&buf, L"git.cmd", subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ /* directories where git is installed according to registry */
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_global_dirs(git_buf *out)
+{
+ static const wchar_t *global_tmpls[4] = {
+ L"%HOME%\\",
+ L"%HOMEDRIVE%%HOMEPATH%\\",
+ L"%USERPROFILE%\\",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls);
+}
+
+int git_win32__find_xdg_dirs(git_buf *out)
+{
+ static const wchar_t *global_tmpls[7] = {
+ L"%XDG_CONFIG_HOME%\\git",
+ L"%APPDATA%\\git",
+ L"%LOCALAPPDATA%\\git",
+ L"%HOME%\\.config\\git",
+ L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
+ L"%USERPROFILE%\\.config\\git",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls);
+}
+
+int git_win32__find_programdata_dirs(git_buf *out)
+{
+ static const wchar_t *programdata_tmpls[2] = {
+ L"%PROGRAMDATA%\\Git",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, programdata_tmpls);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_findfile_h__
+#define INCLUDE_git_findfile_h__
+
+extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath);
+extern int git_win32__find_global_dirs(git_buf *out);
+extern int git_win32__find_xdg_dirs(git_buf *out);
+extern int git_win32__find_programdata_dirs(git_buf *out);
+
+#endif
+
--- /dev/null
+#include <winver.h>
+#include "../../include/git2/version.h"
+
+#ifndef LIBGIT2_FILENAME
+# define LIBGIT2_FILENAME "git2"
+#endif
+
+#ifndef LIBGIT2_COMMENTS
+# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/"
+#endif
+
+VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE
+ FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH
+ PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ //language ID = U.S. English, char set = Windows, Multilingual
+ BEGIN
+ VALUE "FileDescription", "libgit2 - the Git linkable library\0"
+ VALUE "FileVersion", LIBGIT2_VERSION "\0"
+ VALUE "InternalName", LIBGIT2_FILENAME ".dll\0"
+ VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0"
+ VALUE "OriginalFilename", LIBGIT2_FILENAME ".dll\0"
+ VALUE "ProductName", "libgit2\0"
+ VALUE "ProductVersion", LIBGIT2_VERSION "\0"
+ VALUE "Comments", LIBGIT2_COMMENTS "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "map.h"
+#include <errno.h>
+
+#ifndef NO_MMAP
+
+static DWORD get_page_size(void)
+{
+ static DWORD page_size;
+ SYSTEM_INFO sys;
+
+ if (!page_size) {
+ GetSystemInfo(&sys);
+ page_size = sys.dwPageSize;
+ }
+
+ return page_size;
+}
+
+static DWORD get_allocation_granularity(void)
+{
+ static DWORD granularity;
+ SYSTEM_INFO sys;
+
+ if (!granularity) {
+ GetSystemInfo(&sys);
+ granularity = sys.dwAllocationGranularity;
+ }
+
+ return granularity;
+}
+
+int git__page_size(size_t *page_size)
+{
+ *page_size = get_page_size();
+ return 0;
+}
+
+int git__mmap_alignment(size_t *page_size)
+{
+ *page_size = get_allocation_granularity();
+ return 0;
+}
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+ DWORD alignment = get_allocation_granularity();
+ DWORD fmap_prot = 0;
+ DWORD view_prot = 0;
+ DWORD off_low = 0;
+ DWORD off_hi = 0;
+ git_off_t page_start;
+ git_off_t page_offset;
+
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+ out->fmh = NULL;
+
+ if (fh == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value");
+ return -1;
+ }
+
+ if (prot & GIT_PROT_WRITE)
+ fmap_prot |= PAGE_READWRITE;
+ else if (prot & GIT_PROT_READ)
+ fmap_prot |= PAGE_READONLY;
+
+ if (prot & GIT_PROT_WRITE)
+ view_prot |= FILE_MAP_WRITE;
+ if (prot & GIT_PROT_READ)
+ view_prot |= FILE_MAP_READ;
+
+ page_start = (offset / alignment) * alignment;
+ page_offset = offset - page_start;
+
+ if (page_offset != 0) { /* offset must be multiple of the allocation granularity */
+ errno = EINVAL;
+ giterr_set(GITERR_OS, "Failed to mmap. Offset must be multiple of allocation granularity");
+ return -1;
+ }
+
+ out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL);
+ if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) {
+ giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value");
+ out->fmh = NULL;
+ return -1;
+ }
+
+ assert(sizeof(git_off_t) == 8);
+
+ off_low = (DWORD)(page_start);
+ off_hi = (DWORD)(page_start >> 32);
+ out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len);
+ if (!out->data) {
+ giterr_set(GITERR_OS, "Failed to mmap. No data written");
+ CloseHandle(out->fmh);
+ out->fmh = NULL;
+ return -1;
+ }
+ out->len = len;
+
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ int error = 0;
+
+ assert(map != NULL);
+
+ if (map->data) {
+ if (!UnmapViewOfFile(map->data)) {
+ giterr_set(GITERR_OS, "Failed to munmap. Could not unmap view of file");
+ error = -1;
+ }
+ map->data = NULL;
+ }
+
+ if (map->fmh) {
+ if (!CloseHandle(map->fmh)) {
+ giterr_set(GITERR_OS, "Failed to munmap. Could not close handle");
+ error = -1;
+ }
+ map->fmh = NULL;
+ }
+
+ return error;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_mingw_compat__
+#define INCLUDE_mingw_compat__
+
+#if defined(__MINGW32__)
+
+#undef stat
+
+#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR)
+#undef MemoryBarrier
+void __mingworg_MemoryBarrier(void);
+#define MemoryBarrier __mingworg_MemoryBarrier
+#define VOLUME_NAME_DOS 0x0
+#endif
+
+#endif
+
+#endif /* INCLUDE_mingw_compat__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_msvc_compat__
+#define INCLUDE_msvc_compat__
+
+#if defined(_MSC_VER)
+
+typedef unsigned short mode_t;
+typedef SSIZE_T ssize_t;
+
+#define strcasecmp(s1, s2) _stricmp(s1, s2)
+#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c)
+
+#endif
+
+#define GIT_STDLIB_CALL __cdecl
+
+#endif /* INCLUDE_msvc_compat__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
+
+#define PATH__NT_NAMESPACE L"\\\\?\\"
+#define PATH__NT_NAMESPACE_LEN 4
+
+#define PATH__ABSOLUTE_LEN 3
+
+#define path__is_dirsep(p) ((p) == '/' || (p) == '\\')
+
+#define path__is_absolute(p) \
+ (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/'))
+
+#define path__is_nt_namespace(p) \
+ (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \
+ ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/'))
+
+#define path__is_unc(p) \
+ (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
+
+GIT_INLINE(int) path__cwd(wchar_t *path, int size)
+{
+ int len;
+
+ if ((len = GetCurrentDirectoryW(size, path)) == 0) {
+ errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT;
+ return -1;
+ } else if (len > size) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* The Win32 APIs may return "\\?\" once you've used it first.
+ * But it may not. What a gloriously predictible API!
+ */
+ if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN))
+ return len;
+
+ len -= PATH__NT_NAMESPACE_LEN;
+
+ memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len);
+ return len;
+}
+
+static wchar_t *path__skip_server(wchar_t *path)
+{
+ wchar_t *c;
+
+ for (c = path; *c; c++) {
+ if (path__is_dirsep(*c))
+ return c + 1;
+ }
+
+ return c;
+}
+
+static wchar_t *path__skip_prefix(wchar_t *path)
+{
+ if (path__is_nt_namespace(path)) {
+ path += PATH__NT_NAMESPACE_LEN;
+
+ if (wcsncmp(path, L"UNC\\", 4) == 0)
+ path = path__skip_server(path + 4);
+ else if (path__is_absolute(path))
+ path += PATH__ABSOLUTE_LEN;
+ } else if (path__is_absolute(path)) {
+ path += PATH__ABSOLUTE_LEN;
+ } else if (path__is_unc(path)) {
+ path = path__skip_server(path + 2);
+ }
+
+ return path;
+}
+
+int git_win32_path_canonicalize(git_win32_path path)
+{
+ wchar_t *base, *from, *to, *next;
+ size_t len;
+
+ base = to = path__skip_prefix(path);
+
+ /* Unposixify if the prefix */
+ for (from = path; from < to; from++) {
+ if (*from == L'/')
+ *from = L'\\';
+ }
+
+ while (*from) {
+ for (next = from; *next; ++next) {
+ if (*next == L'/') {
+ *next = L'\\';
+ break;
+ }
+
+ if (*next == L'\\')
+ break;
+ }
+
+ len = next - from;
+
+ if (len == 1 && from[0] == L'.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == L'.' && from[1] == L'.') {
+ if (to == base) {
+ /* no more path segments to strip, eat the "../" */
+ if (*next == L'\\')
+ len++;
+
+ base = to;
+ } else {
+ /* back up a path segment */
+ while (to > base && to[-1] == L'\\') to--;
+ while (to > base && to[-1] != L'\\') to--;
+ }
+ } else {
+ if (*next == L'\\' && *from != L'\\')
+ len++;
+
+ if (to != from)
+ memmove(to, from, sizeof(wchar_t) * len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == L'\\') from++;
+ }
+
+ /* Strip trailing backslashes */
+ while (to > base && to[-1] == L'\\') to--;
+
+ *to = L'\0';
+
+ return (to - path);
+}
+
+int git_win32_path__cwd(wchar_t *out, size_t len)
+{
+ int cwd_len;
+
+ if ((cwd_len = path__cwd(out, len)) < 0)
+ return -1;
+
+ /* UNC paths */
+ if (wcsncmp(L"\\\\", out, 2) == 0) {
+ /* Our buffer must be at least 5 characters larger than the
+ * current working directory: we swallow one of the leading
+ * '\'s, but we we add a 'UNC' specifier to the path, plus
+ * a trailing directory separator, plus a NUL.
+ */
+ if (cwd_len > MAX_PATH - 4) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memmove(out+2, out, sizeof(wchar_t) * cwd_len);
+ out[0] = L'U';
+ out[1] = L'N';
+ out[2] = L'C';
+
+ cwd_len += 2;
+ }
+
+ /* Our buffer must be at least 2 characters larger than the current
+ * working directory. (One character for the directory separator,
+ * one for the null.
+ */
+ else if (cwd_len > MAX_PATH - 2) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ return cwd_len;
+}
+
+int git_win32_path_from_utf8(git_win32_path out, const char *src)
+{
+ wchar_t *dest = out;
+
+ /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */
+ memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN);
+ dest += PATH__NT_NAMESPACE_LEN;
+
+ /* See if this is an absolute path (beginning with a drive letter) */
+ if (path__is_absolute(src)) {
+ if (git__utf8_to_16(dest, MAX_PATH, src) < 0)
+ goto on_error;
+ }
+ /* File-prefixed NT-style paths beginning with \\?\ */
+ else if (path__is_nt_namespace(src)) {
+ /* Skip the NT prefix, the destination already contains it */
+ if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0)
+ goto on_error;
+ }
+ /* UNC paths */
+ else if (path__is_unc(src)) {
+ memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4);
+ dest += 4;
+
+ /* Skip the leading "\\" */
+ if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0)
+ goto on_error;
+ }
+ /* Absolute paths omitting the drive letter */
+ else if (src[0] == '\\' || src[0] == '/') {
+ if (path__cwd(dest, MAX_PATH) < 0)
+ goto on_error;
+
+ if (!path__is_absolute(dest)) {
+ errno = ENOENT;
+ goto on_error;
+ }
+
+ /* Skip the drive letter specification ("C:") */
+ if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0)
+ goto on_error;
+ }
+ /* Relative paths */
+ else {
+ int cwd_len;
+
+ if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0)
+ goto on_error;
+
+ dest[cwd_len++] = L'\\';
+
+ if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0)
+ goto on_error;
+ }
+
+ return git_win32_path_canonicalize(out);
+
+on_error:
+ /* set windows error code so we can use its error message */
+ if (errno == ENAMETOOLONG)
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+
+ return -1;
+}
+
+int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
+{
+ char *out = dest;
+ int len;
+
+ /* Strip NT namespacing "\\?\" */
+ if (path__is_nt_namespace(src)) {
+ src += 4;
+
+ /* "\\?\UNC\server\share" -> "\\server\share" */
+ if (wcsncmp(src, L"UNC\\", 4) == 0) {
+ src += 4;
+
+ memcpy(dest, "\\\\", 2);
+ out = dest + 2;
+ }
+ }
+
+ if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0)
+ return len;
+
+ git_path_mkposix(dest);
+
+ return len;
+}
+
+char *git_win32_path_8dot3_name(const char *path)
+{
+ git_win32_path longpath, shortpath;
+ wchar_t *start;
+ char *shortname;
+ int len, namelen = 1;
+
+ if (git_win32_path_from_utf8(longpath, path) < 0)
+ return NULL;
+
+ len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16);
+
+ while (len && shortpath[len-1] == L'\\')
+ shortpath[--len] = L'\0';
+
+ if (len == 0 || len >= GIT_WIN_PATH_UTF16)
+ return NULL;
+
+ for (start = shortpath + (len - 1);
+ start > shortpath && *(start-1) != '/' && *(start-1) != '\\';
+ start--)
+ namelen++;
+
+ /* We may not have actually been given a short name. But if we have,
+ * it will be in the ASCII byte range, so we don't need to worry about
+ * multi-byte sequences and can allocate naively.
+ */
+ if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL)
+ return NULL;
+
+ if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0)
+ return NULL;
+
+ return shortname;
+}
+
+static bool path_is_volume(wchar_t *target, size_t target_len)
+{
+ return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
+}
+
+/* On success, returns the length, in characters, of the path stored in dest.
+* On failure, returns a negative value. */
+int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
+{
+ BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
+ HANDLE handle = NULL;
+ DWORD ioctl_ret;
+ wchar_t *target;
+ size_t target_len;
+
+ int error = -1;
+
+ handle = CreateFileW(path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ switch (reparse_buf->ReparseTag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
+ (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ target = reparse_buf->MountPointReparseBuffer.PathBuffer +
+ (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ default:
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ if (path_is_volume(target, target_len)) {
+ /* This path is a reparse point that represents another volume mounted
+ * at this location, it is not a symbolic link our input was canonical.
+ */
+ errno = EINVAL;
+ error = -1;
+ } else if (target_len) {
+ /* The path may need to have a prefix removed. */
+ target_len = git_win32__canonicalize_path(target, target_len);
+
+ /* Need one additional character in the target buffer
+ * for the terminating NULL. */
+ if (GIT_WIN_PATH_UTF16 > target_len) {
+ wcscpy(dest, target);
+ error = (int)target_len;
+ }
+ }
+
+on_error:
+ CloseHandle(handle);
+ return error;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_path_w32_h__
+#define INCLUDE_git_path_w32_h__
+
+#include "common.h"
+#include "vector.h"
+
+/*
+ * Provides a large enough buffer to support Windows paths: MAX_PATH is
+ * 260, corresponding to a maximum path length of 259 characters plus a
+ * NULL terminator. Prefixing with "\\?\" adds 4 characters, but if the
+ * original was a UNC path, then we turn "\\server\share" into
+ * "\\?\UNC\server\share". So we replace the first two characters with
+ * 8 characters, a net gain of 6, so the maximum length is MAX_PATH+6.
+ */
+#define GIT_WIN_PATH_UTF16 MAX_PATH+6
+
+/* Maximum size of a UTF-8 Win32 path. We remove the "\\?\" or "\\?\UNC\"
+ * prefixes for presentation, bringing us back to 259 (non-NULL)
+ * characters. UTF-8 does have 4-byte sequences, but they are encoded in
+ * UTF-16 using surrogate pairs, which takes up the space of two characters.
+ * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8
+ * (6 bytes) than one surrogate pair (4 bytes).
+ */
+#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
+
+/*
+ * The length of a Windows "shortname", for 8.3 compatibility.
+ */
+#define GIT_WIN_PATH_SHORTNAME 13
+
+/* Win32 path types */
+typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
+typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
+
+/**
+ * Create a Win32 path (in UCS-2 format) from a UTF-8 string.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+extern int git_win32_path_from_utf8(git_win32_path dest, const char *src);
+
+/**
+ * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the
+ * Win32 APIs: remove multiple directory separators, squashing to a single one,
+ * strip trailing directory separators, ensure directory separators are all
+ * canonical (always backslashes, never forward slashes) and process any
+ * directory entries of '.' or '..'.
+ *
+ * This processes the buffer in place.
+ *
+ * @param path The buffer to process
+ * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator)
+ */
+extern int git_win32_path_canonicalize(git_win32_path path);
+
+/**
+ * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src);
+
+/**
+ * Get the short name for the terminal path component in the given path.
+ * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name
+ * for the file "Asdf.txt".
+ *
+ * @param path The given path in UTF-8
+ * @return The name of the shortname for the given path
+ */
+extern char *git_win32_path_8dot3_name(const char *path);
+
+extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_posix__w32_h__
+#define INCLUDE_posix__w32_h__
+
+#include "common.h"
+#include "../posix.h"
+#include "win32-compat.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "dir.h"
+
+typedef SOCKET GIT_SOCKET;
+
+#define p_lseek(f,n,w) _lseeki64(f, n, w)
+
+extern int p_fstat(int fd, struct stat *buf);
+extern int p_lstat(const char *file_name, struct stat *buf);
+extern int p_stat(const char* path, struct stat *buf);
+
+extern int p_utimes(const char *filename, const struct p_timeval times[2]);
+extern int p_futimes(int fd, const struct p_timeval times[2]);
+
+extern int p_readlink(const char *path, char *buf, size_t bufsiz);
+extern int p_symlink(const char *old, const char *new);
+extern int p_link(const char *old, const char *new);
+extern int p_unlink(const char *path);
+extern int p_mkdir(const char *path, mode_t mode);
+extern int p_fsync(int fd);
+extern char *p_realpath(const char *orig_path, char *buffer);
+
+extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
+extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
+extern int p_inet_pton(int af, const char* src, void* dst);
+
+extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
+extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4);
+extern int p_mkstemp(char *tmp_path);
+extern int p_chdir(const char* path);
+extern int p_chmod(const char* path, mode_t mode);
+extern int p_rmdir(const char* path);
+extern int p_access(const char* path, mode_t mode);
+extern int p_ftruncate(int fd, git_off_t size);
+
+/* p_lstat is almost but not quite POSIX correct. Specifically, the use of
+ * ENOTDIR is wrong, in that it does not mean precisely that a non-directory
+ * entry was encountered. Making it correct is potentially expensive,
+ * however, so this is a separate version of p_lstat to use when correct
+ * POSIX ENOTDIR semantics is required.
+ */
+extern int p_lstat_posixly(const char *filename, struct stat *buf);
+
+extern struct tm * p_localtime_r(const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result);
+
+/* Use the bundled regcomp */
+#define p_regcomp regcomp
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "../posix.h"
+#include "../fileops.h"
+#include "path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "repository.h"
+#include "reparse.h"
+#include "global.h"
+#include "buffer.h"
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+#include <ws2tcpip.h>
+
+#ifndef FILE_NAME_NORMALIZED
+# define FILE_NAME_NORMALIZED 0
+#endif
+
+#ifndef IO_REPARSE_TAG_SYMLINK
+#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
+#endif
+
+/* Options which we always provide to _wopen.
+ *
+ * _O_BINARY - Raw access; no translation of CR or LF characters
+ * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes.
+ * The Windows default is 'not inheritable', but the CRT's default (following
+ * POSIX convention) is 'inheritable'. We have no desire for our handles to be
+ * inheritable on Windows, so specify the flag to get default behavior back. */
+#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT)
+
+/* Allowable mode bits on Win32. Using mode bits that are not supported on
+ * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
+ * so we simply remove them.
+ */
+#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
+
+/* GetFinalPathNameByHandleW signature */
+typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
+
+/**
+ * Truncate or extend file.
+ *
+ * We now take a "git_off_t" rather than "long" because
+ * files may be longer than 2Gb.
+ */
+int p_ftruncate(int fd, git_off_t size)
+{
+ if (size < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
+ return ((_chsize_s(fd, size) == 0) ? 0 : -1);
+#else
+ /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */
+ if (size > INT32_MAX) {
+ errno = EFBIG;
+ return -1;
+ }
+ return _chsize(fd, (long)size);
+#endif
+}
+
+int p_mkdir(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ GIT_UNUSED(mode);
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wmkdir(buf);
+}
+
+int p_link(const char *old, const char *new)
+{
+ GIT_UNUSED(old);
+ GIT_UNUSED(new);
+ errno = ENOSYS;
+ return -1;
+}
+
+int p_unlink(const char *path)
+{
+ git_win32_path buf;
+ int error;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ error = _wunlink(buf);
+
+ /* If the file could not be deleted because it was
+ * read-only, clear the bit and try again */
+ if (error == -1 && errno == EACCES) {
+ _wchmod(buf, 0666);
+ error = _wunlink(buf);
+ }
+
+ return error;
+}
+
+int p_fsync(int fd)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ if (fh == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (!FlushFileBuffers(fh)) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INVALID_HANDLE)
+ errno = EINVAL;
+ else
+ errno = EIO;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
+
+static int lstat_w(
+ wchar_t *path,
+ struct stat *buf,
+ bool posix_enotdir)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+
+ if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
+ if (!buf)
+ return 0;
+
+ return git_win32__file_attribute_to_stat(buf, &fdata, path);
+ }
+
+ switch (GetLastError()) {
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ default:
+ errno = ENOENT;
+ break;
+ }
+
+ /* To match POSIX behavior, set ENOTDIR when any of the folders in the
+ * file path is a regular file, otherwise set ENOENT.
+ */
+ if (errno == ENOENT && posix_enotdir) {
+ size_t path_len = wcslen(path);
+
+ /* scan up path until we find an existing item */
+ while (1) {
+ DWORD attrs;
+
+ /* remove last directory component */
+ for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
+
+ if (path_len <= 0)
+ break;
+
+ path[path_len] = L'\0';
+ attrs = GetFileAttributesW(path);
+
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ break;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
+ return -1;
+
+ git_win32__path_trim_end(path_w, len);
+
+ return lstat_w(path_w, buf, posixly_correct);
+}
+
+int p_lstat(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, false);
+}
+
+int p_lstat_posixly(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, true);
+}
+
+int p_utimes(const char *filename, const struct p_timeval times[2])
+{
+ int fd, error;
+
+ if ((fd = p_open(filename, O_RDWR)) < 0)
+ return fd;
+
+ error = p_futimes(fd, times);
+
+ close(fd);
+ return error;
+}
+
+int p_futimes(int fd, const struct p_timeval times[2])
+{
+ HANDLE handle;
+ FILETIME atime = {0}, mtime = {0};
+
+ if (times == NULL) {
+ SYSTEMTIME st;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &atime);
+ SystemTimeToFileTime(&st, &mtime);
+ } else {
+ git_win32__timeval_to_filetime(&atime, times[0]);
+ git_win32__timeval_to_filetime(&mtime, times[1]);
+ }
+
+ if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
+ return -1;
+
+ return 0;
+}
+
+int p_readlink(const char *path, char *buf, size_t bufsiz)
+{
+ git_win32_path path_w, target_w;
+ git_win32_utf8_path target;
+ int len;
+
+ /* readlink(2) does not NULL-terminate the string written
+ * to the target buffer. Furthermore, the target buffer need
+ * not be large enough to hold the entire result. A truncated
+ * result should be written in this case. Since this truncation
+ * could occur in the middle of the encoding of a code point,
+ * we need to buffer the result on the stack. */
+
+ if (git_win32_path_from_utf8(path_w, path) < 0 ||
+ git_win32_path_readlink_w(target_w, path_w) < 0 ||
+ (len = git_win32_path_to_utf8(target, target_w)) < 0)
+ return -1;
+
+ bufsiz = min((size_t)len, bufsiz);
+ memcpy(buf, target, bufsiz);
+
+ return (int)bufsiz;
+}
+
+int p_symlink(const char *old, const char *new)
+{
+ /* Real symlinks on NTFS require admin privileges. Until this changes,
+ * libgit2 just creates a text file with the link target in the contents.
+ */
+ return git_futils_fake_symlink(old, new);
+}
+
+int p_open(const char *path, int flags, ...)
+{
+ git_win32_path buf;
+ mode_t mode = 0;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode & WIN32_MODE_MASK);
+}
+
+int p_creat(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wopen(buf,
+ _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS,
+ mode & WIN32_MODE_MASK);
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ git_win32_path buf;
+ wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
+
+ if (!cwd)
+ return -1;
+
+ /* Convert the working directory back to UTF-8 */
+ if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INSUFFICIENT_BUFFER)
+ errno = ERANGE;
+ else
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Returns the address of the GetFinalPathNameByHandleW function.
+ * This function is available on Windows Vista and higher.
+ */
+static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
+{
+ static PFGetFinalPathNameByHandleW pFunc = NULL;
+ PFGetFinalPathNameByHandleW toReturn = pFunc;
+
+ if (!toReturn) {
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule)
+ toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
+
+ pFunc = toReturn;
+ }
+
+ assert(toReturn);
+
+ return toReturn;
+}
+
+static int getfinalpath_w(
+ git_win32_path dest,
+ const wchar_t *path)
+{
+ PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
+ HANDLE hFile;
+ DWORD dwChars;
+
+ if (!pgfp)
+ return -1;
+
+ /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
+ * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
+ * target of the link. */
+ hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (INVALID_HANDLE_VALUE == hFile)
+ return -1;
+
+ /* Call GetFinalPathNameByHandle */
+ dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
+ CloseHandle(hFile);
+
+ if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
+ return -1;
+
+ /* The path may be delivered to us with a prefix; canonicalize */
+ return (int)git_win32__canonicalize_path(dest, dwChars);
+}
+
+static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
+{
+ git_win32_path target_w;
+
+ if (getfinalpath_w(target_w, path) < 0)
+ return -1;
+
+ return lstat_w(target_w, buf, false);
+}
+
+int p_fstat(int fd, struct stat *buf)
+{
+ BY_HANDLE_FILE_INFORMATION fhInfo;
+
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ if (fh == INVALID_HANDLE_VALUE ||
+ !GetFileInformationByHandle(fh, &fhInfo)) {
+ errno = EBADF;
+ return -1;
+ }
+
+ git_win32__file_information_to_stat(buf, &fhInfo);
+ return 0;
+}
+
+int p_stat(const char* path, struct stat* buf)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
+ lstat_w(path_w, buf, false) < 0)
+ return -1;
+
+ /* The item is a symbolic link or mount point. No need to iterate
+ * to follow multiple links; use GetFinalPathNameFromHandle. */
+ if (S_ISLNK(buf->st_mode))
+ return follow_and_lstat_link(path_w, buf);
+
+ return 0;
+}
+
+int p_chdir(const char* path)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wchdir(buf);
+}
+
+int p_chmod(const char* path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wchmod(buf, mode);
+}
+
+int p_rmdir(const char* path)
+{
+ git_win32_path buf;
+ int error;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ error = _wrmdir(buf);
+
+ if (error == -1) {
+ switch (GetLastError()) {
+ /* _wrmdir() is documented to return EACCES if "A program has an open
+ * handle to the directory." This sounds like what everybody else calls
+ * EBUSY. Let's convert appropriate error codes.
+ */
+ case ERROR_SHARING_VIOLATION:
+ errno = EBUSY;
+ break;
+
+ /* This error can be returned when trying to rmdir an extant file. */
+ case ERROR_DIRECTORY:
+ errno = ENOTDIR;
+ break;
+ }
+ }
+
+ return error;
+}
+
+char *p_realpath(const char *orig_path, char *buffer)
+{
+ git_win32_path orig_path_w, buffer_w;
+
+ if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
+ return NULL;
+
+ /* Note that if the path provided is a relative path, then the current directory
+ * is used to resolve the path -- which is a concurrency issue because the current
+ * directory is a process-wide variable. */
+ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+
+ return NULL;
+ }
+
+ /* The path must exist. */
+ if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Convert the path to UTF-8. If the caller provided a buffer, then it
+ * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
+ * then we may overflow. */
+ if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
+ return NULL;
+
+ git_path_mkposix(buffer);
+
+ return buffer;
+}
+
+int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
+{
+#if defined(_MSC_VER)
+ int len;
+
+ if (count == 0)
+ return _vscprintf(format, argptr);
+
+ #if _MSC_VER >= 1500
+ len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
+ #else
+ len = _vsnprintf(buffer, count, format, argptr);
+ #endif
+
+ if (len < 0)
+ return _vscprintf(format, argptr);
+
+ return len;
+#else /* MinGW */
+ return vsnprintf(buffer, count, format, argptr);
+#endif
+}
+
+int p_snprintf(char *buffer, size_t count, const char *format, ...)
+{
+ va_list va;
+ int r;
+
+ va_start(va, format);
+ r = p_vsnprintf(buffer, count, format, va);
+ va_end(va);
+
+ return r;
+}
+
+/* TODO: wut? */
+int p_mkstemp(char *tmp_path)
+{
+#if defined(_MSC_VER) && _MSC_VER >= 1500
+ if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
+ return -1;
+#else
+ if (_mktemp(tmp_path) == NULL)
+ return -1;
+#endif
+
+ return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536
+}
+
+int p_access(const char* path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _waccess(buf, mode & WIN32_MODE_MASK);
+}
+
+static int ensure_writable(wchar_t *fpath)
+{
+ DWORD attrs;
+
+ attrs = GetFileAttributesW(fpath);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND)
+ return 0;
+
+ giterr_set(GITERR_OS, "failed to get attributes");
+ return -1;
+ }
+
+ if (!(attrs & FILE_ATTRIBUTE_READONLY))
+ return 0;
+
+ attrs &= ~FILE_ATTRIBUTE_READONLY;
+ if (!SetFileAttributesW(fpath, attrs)) {
+ giterr_set(GITERR_OS, "failed to set attributes");
+ return -1;
+ }
+
+ return 0;
+}
+
+int p_rename(const char *from, const char *to)
+{
+ git_win32_path wfrom;
+ git_win32_path wto;
+ int rename_tries;
+ int rename_succeeded;
+ int error;
+
+ if (git_win32_path_from_utf8(wfrom, from) < 0 ||
+ git_win32_path_from_utf8(wto, to) < 0)
+ return -1;
+
+ /* wait up to 50ms if file is locked by another thread or process */
+ rename_tries = 0;
+ rename_succeeded = 0;
+ while (rename_tries < 10) {
+ if (ensure_writable(wto) == 0 &&
+ MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
+ rename_succeeded = 1;
+ break;
+ }
+
+ error = GetLastError();
+ if (error == ERROR_SHARING_VIOLATION || error == ERROR_ACCESS_DENIED) {
+ Sleep(5);
+ rename_tries++;
+ } else
+ break;
+ }
+
+ return rename_succeeded ? 0 : -1;
+}
+
+int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* giterr_set will be done by caller */
+
+ return recv(socket, buffer, (int)length, flags);
+}
+
+int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* giterr_set will be done by caller */
+
+ return send(socket, buffer, (int)length, flags);
+}
+
+/**
+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
+ */
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+
+int p_inet_pton(int af, const char *src, void *dst)
+{
+ struct sockaddr_storage sin;
+ void *addr;
+ int sin_len = sizeof(struct sockaddr_storage), addr_len;
+ int error = 0;
+
+ if (af == AF_INET) {
+ addr = &((struct sockaddr_in *)&sin)->sin_addr;
+ addr_len = sizeof(struct in_addr);
+ } else if (af == AF_INET6) {
+ addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
+ addr_len = sizeof(struct in6_addr);
+ } else {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
+ memcpy(dst, addr, addr_len);
+ return 1;
+ }
+
+ switch(WSAGetLastError()) {
+ case WSAEINVAL:
+ return 0;
+ case WSAEFAULT:
+ errno = ENOSPC;
+ return -1;
+ case WSA_NOT_ENOUGH_MEMORY:
+ errno = ENOMEM;
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
--- /dev/null
+#include "precompiled.h"
--- /dev/null
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <regex.h>
+
+#include <io.h>
+#include <direct.h>
+#ifdef GIT_THREADS
+ #include "win32/thread.h"
+#endif
+
+#include "git2.h"
+#include "common.h"
--- /dev/null
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+
+#ifndef INCLUDE_git_win32_reparse_h__
+#define INCLUDE_git_win32_reparse_h__
+
+/* This structure is defined on MSDN at
+* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+*
+* It was formerly included in the Windows 2000 SDK and remains defined in
+* MinGW, so we must define it with a silly name to avoid conflicting.
+*/
+typedef struct _GIT_REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} GIT_REPARSE_DATA_BUFFER;
+
+#define REPARSE_DATA_HEADER_SIZE 8
+#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
+#define REPARSE_DATA_UNION_SIZE 12
+
+/* Missing in MinGW */
+#ifndef FSCTL_GET_REPARSE_POINT
+# define FSCTL_GET_REPARSE_POINT 0x000900a8
+#endif
+
+/* Missing in MinGW */
+#ifndef FSCTL_SET_REPARSE_POINT
+# define FSCTL_SET_REPARSE_POINT 0x000900a4
+#endif
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "thread.h"
+#include "../global.h"
+
+#define CLEAN_THREAD_EXIT 0x6F012842
+
+typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *);
+
+static win32_srwlock_fn win32_srwlock_initialize;
+static win32_srwlock_fn win32_srwlock_acquire_shared;
+static win32_srwlock_fn win32_srwlock_release_shared;
+static win32_srwlock_fn win32_srwlock_acquire_exclusive;
+static win32_srwlock_fn win32_srwlock_release_exclusive;
+
+/* The thread procedure stub used to invoke the caller's procedure
+ * and capture the return value for later collection. Windows will
+ * only hold a DWORD, but we need to be able to store an entire
+ * void pointer. This requires the indirection. */
+static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
+{
+ git_thread *thread = lpParameter;
+
+ /* Set the current thread for `git_thread_exit` */
+ GIT_GLOBAL->current_thread = thread;
+
+ thread->result = thread->proc(thread->param);
+
+ git__free_tls_data();
+
+ return CLEAN_THREAD_EXIT;
+}
+
+int git_threads_init(void)
+{
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule) {
+ win32_srwlock_initialize = (win32_srwlock_fn)
+ GetProcAddress(hModule, "InitializeSRWLock");
+ win32_srwlock_acquire_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockShared");
+ win32_srwlock_release_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockShared");
+ win32_srwlock_acquire_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockExclusive");
+ win32_srwlock_release_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockExclusive");
+ }
+
+ return 0;
+}
+
+int git_thread_create(
+ git_thread *GIT_RESTRICT thread,
+ void *(*start_routine)(void*),
+ void *GIT_RESTRICT arg)
+{
+ thread->result = NULL;
+ thread->param = arg;
+ thread->proc = start_routine;
+ thread->thread = CreateThread(
+ NULL, 0, git_win32__threadproc, thread, 0, NULL);
+
+ return thread->thread ? 0 : -1;
+}
+
+int git_thread_join(
+ git_thread *thread,
+ void **value_ptr)
+{
+ DWORD exit;
+
+ if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0)
+ return -1;
+
+ if (!GetExitCodeThread(thread->thread, &exit)) {
+ CloseHandle(thread->thread);
+ return -1;
+ }
+
+ /* Check for the thread having exited uncleanly. If exit was unclean,
+ * then we don't have a return value to give back to the caller. */
+ if (exit != CLEAN_THREAD_EXIT) {
+ assert(false);
+ thread->result = NULL;
+ }
+
+ if (value_ptr)
+ *value_ptr = thread->result;
+
+ CloseHandle(thread->thread);
+ return 0;
+}
+
+void git_thread_exit(void *value)
+{
+ assert(GIT_GLOBAL->current_thread);
+ GIT_GLOBAL->current_thread->result = value;
+
+ git__free_tls_data();
+
+ ExitThread(CLEAN_THREAD_EXIT);
+}
+
+size_t git_thread_currentid(void)
+{
+ return GetCurrentThreadId();
+}
+
+int git_mutex_init(git_mutex *GIT_RESTRICT mutex)
+{
+ InitializeCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_free(git_mutex *mutex)
+{
+ DeleteCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_lock(git_mutex *mutex)
+{
+ EnterCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_unlock(git_mutex *mutex)
+{
+ LeaveCriticalSection(mutex);
+ return 0;
+}
+
+int git_cond_init(git_cond *cond)
+{
+ /* This is an auto-reset event. */
+ *cond = CreateEventW(NULL, FALSE, FALSE, NULL);
+ assert(*cond);
+
+ /* If we can't create the event, claim that the reason was out-of-memory.
+ * The actual reason can be fetched with GetLastError(). */
+ return *cond ? 0 : ENOMEM;
+}
+
+int git_cond_free(git_cond *cond)
+{
+ BOOL closed;
+
+ if (!cond)
+ return EINVAL;
+
+ closed = CloseHandle(*cond);
+ assert(closed);
+ GIT_UNUSED(closed);
+
+ *cond = NULL;
+ return 0;
+}
+
+int git_cond_wait(git_cond *cond, git_mutex *mutex)
+{
+ int error;
+ DWORD wait_result;
+
+ if (!cond || !mutex)
+ return EINVAL;
+
+ /* The caller must be holding the mutex. */
+ error = git_mutex_unlock(mutex);
+
+ if (error)
+ return error;
+
+ wait_result = WaitForSingleObject(*cond, INFINITE);
+ assert(WAIT_OBJECT_0 == wait_result);
+ GIT_UNUSED(wait_result);
+
+ return git_mutex_lock(mutex);
+}
+
+int git_cond_signal(git_cond *cond)
+{
+ BOOL signaled;
+
+ if (!cond)
+ return EINVAL;
+
+ signaled = SetEvent(*cond);
+ assert(signaled);
+ GIT_UNUSED(signaled);
+
+ return 0;
+}
+
+int git_rwlock_init(git_rwlock *GIT_RESTRICT lock)
+{
+ if (win32_srwlock_initialize)
+ win32_srwlock_initialize(&lock->native.srwl);
+ else
+ InitializeCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_rdlock(git_rwlock *lock)
+{
+ if (win32_srwlock_acquire_shared)
+ win32_srwlock_acquire_shared(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_rdunlock(git_rwlock *lock)
+{
+ if (win32_srwlock_release_shared)
+ win32_srwlock_release_shared(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_wrlock(git_rwlock *lock)
+{
+ if (win32_srwlock_acquire_exclusive)
+ win32_srwlock_acquire_exclusive(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_wrunlock(git_rwlock *lock)
+{
+ if (win32_srwlock_release_exclusive)
+ win32_srwlock_release_exclusive(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_free(git_rwlock *lock)
+{
+ if (!win32_srwlock_initialize)
+ DeleteCriticalSection(&lock->native.csec);
+ git__memzero(lock, sizeof(*lock));
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_win32_thread_h__
+#define INCLUDE_win32_thread_h__
+
+#include "../common.h"
+
+#if defined (_MSC_VER)
+# define GIT_RESTRICT __restrict
+#else
+# define GIT_RESTRICT __restrict__
+#endif
+
+typedef struct {
+ HANDLE thread;
+ void *(*proc)(void *);
+ void *param;
+ void *result;
+} git_thread;
+
+typedef CRITICAL_SECTION git_mutex;
+typedef HANDLE git_cond;
+
+typedef struct { void *Ptr; } GIT_SRWLOCK;
+
+typedef struct {
+ union {
+ GIT_SRWLOCK srwl;
+ CRITICAL_SECTION csec;
+ } native;
+} git_rwlock;
+
+int git_threads_init(void);
+
+int git_thread_create(git_thread *GIT_RESTRICT,
+ void *(*) (void *),
+ void *GIT_RESTRICT);
+int git_thread_join(git_thread *, void **);
+size_t git_thread_currentid(void);
+void git_thread_exit(void *);
+
+int git_mutex_init(git_mutex *GIT_RESTRICT mutex);
+int git_mutex_free(git_mutex *);
+int git_mutex_lock(git_mutex *);
+int git_mutex_unlock(git_mutex *);
+
+int git_cond_init(git_cond *);
+int git_cond_free(git_cond *);
+int git_cond_wait(git_cond *, git_mutex *);
+int git_cond_signal(git_cond *);
+
+int git_rwlock_init(git_rwlock *GIT_RESTRICT lock);
+int git_rwlock_rdlock(git_rwlock *);
+int git_rwlock_rdunlock(git_rwlock *);
+int git_rwlock_wrlock(git_rwlock *);
+int git_rwlock_wrunlock(git_rwlock *);
+int git_rwlock_free(git_rwlock *);
+
+#endif /* INCLUDE_win32_thread_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "utf-conv.h"
+
+GIT_INLINE(void) git__set_errno(void)
+{
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+}
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+{
+ int len;
+
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
+ if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0)
+ git__set_errno();
+
+ return len;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
+{
+ int len;
+
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
+ if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0)
+ git__set_errno();
+
+ return len;
+}
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
+{
+ int utf16_size;
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
+
+ if (!utf16_size) {
+ git__set_errno();
+ return -1;
+ }
+
+ if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
+
+ if (!utf16_size) {
+ git__set_errno();
+
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf16_size - 1;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
+{
+ int utf8_size;
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL);
+
+ if (!utf8_size) {
+ git__set_errno();
+ return -1;
+ }
+
+ *dest = git__malloc(utf8_size);
+
+ if (!*dest) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL);
+
+ if (!utf8_size) {
+ git__set_errno();
+
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf8_size - 1;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_utfconv_h__
+#define INCLUDE_git_utfconv_h__
+
+#include <wchar.h>
+#include "common.h"
+
+#ifndef WC_ERR_INVALID_CHARS
+# define WC_ERR_INVALID_CHARS 0x80
+#endif
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src);
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_win32_version_h__
+#define INCLUDE_win32_version_h__
+
+#include <windows.h>
+
+GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack)
+{
+ OSVERSIONINFOEX version_test = {0};
+ DWORD version_test_mask;
+ DWORDLONG version_condition_mask = 0;
+
+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ version_test.dwMajorVersion = major;
+ version_test.dwMinorVersion = minor;
+ version_test.wServicePackMajor = (WORD)service_pack;
+ version_test.wServicePackMinor = 0;
+
+ version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);
+
+ VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
+
+ if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))
+ return 0;
+
+ return 1;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "w32_buffer.h"
+#include "../buffer.h"
+#include "utf-conv.h"
+
+GIT_INLINE(int) handle_wc_error(void)
+{
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+
+ return -1;
+}
+
+int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w)
+{
+ int utf8_len, utf8_write_len;
+ size_t new_size;
+
+ if (!len_w)
+ return 0;
+
+ assert(string_w);
+
+ /* Measure the string necessary for conversion */
+ if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, NULL, 0, NULL, NULL)) == 0)
+ return 0;
+
+ assert(utf8_len > 0);
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+
+ if (git_buf_grow(buf, new_size) < 0)
+ return -1;
+
+ if ((utf8_write_len = WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0)
+ return handle_wc_error();
+
+ assert(utf8_write_len == utf8_len);
+
+ buf->size += utf8_write_len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_win32_buffer_h__
+#define INCLUDE_git_win32_buffer_h__
+
+#include "../buffer.h"
+
+/**
+ * Convert a wide character string to UTF-8 and append the results to the
+ * buffer.
+ */
+int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#if defined(GIT_MSVC_CRTDBG)
+#include "w32_stack.h"
+#include "w32_crtdbg_stacktrace.h"
+
+#define CRTDBG_STACKTRACE__UID_LEN (15)
+
+/**
+ * The stacktrace of an allocation can be distilled
+ * to a unique id based upon the stackframe pointers
+ * and ignoring any size arguments. We will use these
+ * UIDs as the (char const*) __FILE__ argument we
+ * give to the CRT malloc routines.
+ */
+typedef struct {
+ char uid[CRTDBG_STACKTRACE__UID_LEN + 1];
+} git_win32__crtdbg_stacktrace__uid;
+
+/**
+ * All mallocs with the same stacktrace will be de-duped
+ * and aggregated into this row.
+ */
+typedef struct {
+ git_win32__crtdbg_stacktrace__uid uid; /* must be first */
+ git_win32__stack__raw_data raw_data;
+ unsigned int count_allocs; /* times this alloc signature seen since init */
+ unsigned int count_allocs_at_last_checkpoint; /* times since last mark */
+ unsigned int transient_count_leaks; /* sum of leaks */
+} git_win32__crtdbg_stacktrace__row;
+
+static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
+
+/**
+ * CRTDBG memory leak tracking takes a "char const * const file_name"
+ * and stores the pointer in the heap data (instead of allocing a copy
+ * for itself). Normally, this is not a problem, since we usually pass
+ * in __FILE__. But I'm going to lie to it and pass in the address of
+ * the UID in place of the file_name. Also, I do not want to alloc the
+ * stacktrace data (because we are called from inside our alloc routines).
+ * Therefore, I'm creating a very large static pool array to store row
+ * data. This also eliminates the temptation to realloc it (and move the
+ * UID pointers).
+ *
+ * And to efficiently look for duplicates we need an index on the rows
+ * so we can bsearch it. Again, without mallocing.
+ *
+ * If we observe more than MY_ROW_LIMIT unique malloc signatures, we
+ * fall through and use the traditional __FILE__ processing and don't
+ * try to de-dup them. If your testing hits this limit, just increase
+ * it and try again.
+ */
+
+#define MY_ROW_LIMIT (1024 * 1024)
+static git_win32__crtdbg_stacktrace__row g_cs_rows[MY_ROW_LIMIT];
+static git_win32__crtdbg_stacktrace__row *g_cs_index[MY_ROW_LIMIT];
+
+static unsigned int g_cs_end = MY_ROW_LIMIT;
+static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */
+static unsigned int g_count_total_allocs = 0; /* number of allocs seen */
+static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */
+static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */
+static bool g_limit_reached = false; /* had allocs after we filled row table */
+
+static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */
+static bool g_transient_leaks_since_mark = false; /* payload for hook */
+
+/**
+ * Compare function for bsearch on g_cs_index table.
+ */
+static int row_cmp(const void *v1, const void *v2)
+{
+ git_win32__stack__raw_data *d1 = (git_win32__stack__raw_data*)v1;
+ git_win32__crtdbg_stacktrace__row *r2 = (git_win32__crtdbg_stacktrace__row *)v2;
+
+ return (git_win32__stack_compare(d1, &r2->raw_data));
+}
+
+/**
+ * Unique insert the new data into the row and index tables.
+ * We have to sort by the stackframe data itself, not the uid.
+ */
+static git_win32__crtdbg_stacktrace__row * insert_unique(
+ const git_win32__stack__raw_data *pdata)
+{
+ size_t pos;
+ if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) {
+ /* Append new unique item to row table. */
+ memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata));
+ sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins);
+
+ /* Insert pointer to it into the proper place in the index table. */
+ if (pos < g_cs_ins)
+ memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0]));
+ g_cs_index[pos] = &g_cs_rows[g_cs_ins];
+
+ g_cs_ins++;
+ }
+
+ g_cs_index[pos]->count_allocs++;
+
+ return g_cs_index[pos];
+}
+
+/**
+ * Hook function to receive leak data from the CRT. (This includes
+ * both "<file_name>:(<line_number>)" data, but also each of the
+ * various headers and fields.
+ *
+ * Scan this for the special "##<pos>" UID forms that we substituted
+ * for the "<file_name>". Map <pos> back to the row data and
+ * increment its leak count.
+ *
+ * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx
+ *
+ * We suppress the actual crtdbg output.
+ */
+static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal)
+{
+ static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */
+ unsigned int pos;
+
+ *retVal = 0; /* do not invoke debugger */
+
+ if ((szMsg[0] != '#') || (szMsg[1] != '#'))
+ return hook_result;
+
+ if (sscanf(&szMsg[2], "%08lx", &pos) < 1)
+ return hook_result;
+ if (pos >= g_cs_ins)
+ return hook_result;
+
+ if (g_transient_leaks_since_mark) {
+ if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint)
+ return hook_result;
+ }
+
+ g_cs_rows[pos].transient_count_leaks++;
+
+ if (g_cs_rows[pos].transient_count_leaks == 1)
+ g_transient_count_dedup_leaks++;
+
+ g_transient_count_total_leaks++;
+
+ return hook_result;
+}
+
+/**
+ * Write leak data to all of the various places we need.
+ * We force the caller to sprintf() the message first
+ * because we want to avoid fprintf() because it allocs.
+ */
+static void my_output(const char *buf)
+{
+ fwrite(buf, strlen(buf), 1, stderr);
+ OutputDebugString(buf);
+}
+
+/**
+ * For each row with leaks, dump a stacktrace for it.
+ */
+static void dump_summary(const char *label)
+{
+ unsigned int k;
+ char buf[10 * 1024];
+
+ if (g_transient_count_total_leaks == 0)
+ return;
+
+ fflush(stdout);
+ fflush(stderr);
+ my_output("\n");
+
+ if (g_limit_reached) {
+ sprintf(buf,
+ "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n",
+ MY_ROW_LIMIT);
+ my_output(buf);
+ }
+
+ if (!label)
+ label = "";
+
+ if (g_transient_leaks_since_mark) {
+ sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n",
+ g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
+ my_output(buf);
+ } else {
+ sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n",
+ g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
+ my_output(buf);
+ }
+ my_output("\n");
+
+ for (k = 0; k < g_cs_ins; k++) {
+ if (g_cs_rows[k].transient_count_leaks > 0) {
+ sprintf(buf, "LEAK: %s leaked %d of %d times:\n",
+ g_cs_rows[k].uid.uid,
+ g_cs_rows[k].transient_count_leaks,
+ g_cs_rows[k].count_allocs);
+ my_output(buf);
+
+ if (git_win32__stack_format(
+ buf, sizeof(buf), &g_cs_rows[k].raw_data,
+ NULL, NULL) >= 0) {
+ my_output(buf);
+ }
+
+ my_output("\n");
+ }
+ }
+
+ fflush(stderr);
+}
+
+void git_win32__crtdbg_stacktrace_init(void)
+{
+ InitializeCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+}
+
+int git_win32__crtdbg_stacktrace__dump(
+ git_win32__crtdbg_stacktrace_options opt,
+ const char *label)
+{
+ _CRT_REPORT_HOOK old;
+ unsigned int k;
+ int r = 0;
+
+#define IS_BIT_SET(o,b) (((o) & (b)) != 0)
+
+ bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK);
+ bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK);
+ bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL);
+ bool b_quiet = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__QUIET);
+
+ if (b_leaks_since_mark && b_leaks_total) {
+ giterr_set(GITERR_INVALID, "Cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL.");
+ return GIT_ERROR;
+ }
+ if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) {
+ giterr_set(GITERR_INVALID, "Nothing to do.");
+ return GIT_ERROR;
+ }
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ if (b_leaks_since_mark || b_leaks_total) {
+ /* All variables with "transient" in the name are per-dump counters
+ * and reset before each dump. This lets us handle checkpoints.
+ */
+ g_transient_count_total_leaks = 0;
+ g_transient_count_dedup_leaks = 0;
+ for (k = 0; k < g_cs_ins; k++) {
+ g_cs_rows[k].transient_count_leaks = 0;
+ }
+ }
+
+ g_transient_leaks_since_mark = b_leaks_since_mark;
+
+ old = _CrtSetReportHook(report_hook);
+ _CrtDumpMemoryLeaks();
+ _CrtSetReportHook(old);
+
+ if (b_leaks_since_mark || b_leaks_total) {
+ r = g_transient_count_dedup_leaks;
+
+ if (!b_quiet)
+ dump_summary(label);
+ }
+
+ if (b_set_mark) {
+ for (k = 0; k < g_cs_ins; k++) {
+ g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs;
+ }
+
+ g_checkpoint_id++;
+ }
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ return r;
+}
+
+void git_win32__crtdbg_stacktrace_cleanup(void)
+{
+ /* At shutdown/cleanup, dump cummulative leak info
+ * with everything since startup. This might generate
+ * extra noise if the caller has been doing checkpoint
+ * dumps, but it might also eliminate some false
+ * positives for resources previously reported during
+ * checkpoints.
+ */
+ git_win32__crtdbg_stacktrace__dump(
+ GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL,
+ "CLEANUP");
+
+ DeleteCriticalSection(&g_crtdbg_stacktrace_cs);
+}
+
+const char *git_win32__crtdbg_stacktrace(int skip, const char *file)
+{
+ git_win32__stack__raw_data new_data;
+ git_win32__crtdbg_stacktrace__row *row;
+ const char * result = file;
+
+ if (git_win32__stack_capture(&new_data, skip+1) < 0)
+ return result;
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ if (g_cs_ins < g_cs_end) {
+ row = insert_unique(&new_data);
+ result = row->uid.uid;
+ } else {
+ g_limit_reached = true;
+ }
+
+ g_count_total_allocs++;
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ return result;
+}
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_w32_crtdbg_stacktrace_h__
+#define INCLUDE_w32_crtdbg_stacktrace_h__
+
+#if defined(GIT_MSVC_CRTDBG)
+
+/**
+ * Initialize our memory leak tracking and de-dup data structures.
+ * This should ONLY be called by git_libgit2_init().
+ */
+void git_win32__crtdbg_stacktrace_init(void);
+
+/**
+ * Shutdown our memory leak tracking and dump summary data.
+ * This should ONLY be called by git_libgit2_shutdown().
+ *
+ * We explicitly call _CrtDumpMemoryLeaks() during here so
+ * that we can compute summary data for the leaks. We print
+ * the stacktrace of each unique leak.
+ *
+ * This cleanup does not happen if the app calls exit()
+ * without calling the libgit2 shutdown code.
+ *
+ * This info we print here is independent of any automatic
+ * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF.
+ * Set it in your app if you also want traditional reporting.
+ */
+void git_win32__crtdbg_stacktrace_cleanup(void);
+
+/**
+ * Checkpoint options.
+ */
+typedef enum git_win32__crtdbg_stacktrace_options {
+ /**
+ * Set checkpoint marker.
+ */
+ GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK = (1 << 0),
+
+ /**
+ * Dump leaks since last checkpoint marker.
+ * May not be combined with __LEAKS_TOTAL.
+ *
+ * Note that this may generate false positives for global TLS
+ * error state and other global caches that aren't cleaned up
+ * until the thread/process terminates. So when using this
+ * around a region of interest, also check the final (at exit)
+ * dump before digging into leaks reported here.
+ */
+ GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK = (1 << 1),
+
+ /**
+ * Dump leaks since init. May not be combined
+ * with __LEAKS_SINCE_MARK.
+ */
+ GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL = (1 << 2),
+
+ /**
+ * Suppress printing during dumps.
+ * Just return leak count.
+ */
+ GIT_WIN32__CRTDBG_STACKTRACE__QUIET = (1 << 3),
+
+} git_win32__crtdbg_stacktrace_options;
+
+/**
+ * Checkpoint memory state and/or dump unique stack traces of
+ * current memory leaks.
+ *
+ * @return number of unique leaks (relative to requested starting
+ * point) or error.
+ */
+GIT_EXTERN(int) git_win32__crtdbg_stacktrace__dump(
+ git_win32__crtdbg_stacktrace_options opt,
+ const char *label);
+
+/**
+ * Construct stacktrace and append it to the global buffer.
+ * Return pointer to start of this string. On any error or
+ * lack of buffer space, just return the given file buffer
+ * so it will behave as usual.
+ *
+ * This should ONLY be called by our internal memory allocations
+ * routines.
+ */
+const char *git_win32__crtdbg_stacktrace(int skip, const char *file);
+
+#endif
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#if defined(GIT_MSVC_CRTDBG)
+#include "Windows.h"
+#include "Dbghelp.h"
+#include "win32/posix.h"
+#include "w32_stack.h"
+#include "hash.h"
+
+/**
+ * This is supposedly defined in WinBase.h (from Windows.h) but there were linker issues.
+ */
+USHORT WINAPI RtlCaptureStackBackTrace(ULONG, ULONG, PVOID*, PULONG);
+
+static bool g_win32_stack_initialized = false;
+static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE;
+static git_win32__stack__aux_cb_alloc g_aux_cb_alloc = NULL;
+static git_win32__stack__aux_cb_lookup g_aux_cb_lookup = NULL;
+
+int git_win32__stack__set_aux_cb(
+ git_win32__stack__aux_cb_alloc cb_alloc,
+ git_win32__stack__aux_cb_lookup cb_lookup)
+{
+ g_aux_cb_alloc = cb_alloc;
+ g_aux_cb_lookup = cb_lookup;
+
+ return 0;
+}
+
+void git_win32__stack_init(void)
+{
+ if (!g_win32_stack_initialized) {
+ g_win32_stack_process = GetCurrentProcess();
+ SymSetOptions(SYMOPT_LOAD_LINES);
+ SymInitialize(g_win32_stack_process, NULL, TRUE);
+ g_win32_stack_initialized = true;
+ }
+}
+
+void git_win32__stack_cleanup(void)
+{
+ if (g_win32_stack_initialized) {
+ SymCleanup(g_win32_stack_process);
+ g_win32_stack_process = INVALID_HANDLE_VALUE;
+ g_win32_stack_initialized = false;
+ }
+}
+
+int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip)
+{
+ if (!g_win32_stack_initialized) {
+ giterr_set(GITERR_INVALID, "git_win32_stack not initialized.");
+ return GIT_ERROR;
+ }
+
+ memset(pdata, 0, sizeof(*pdata));
+ pdata->nr_frames = RtlCaptureStackBackTrace(
+ skip+1, GIT_WIN32__STACK__MAX_FRAMES, pdata->frames, NULL);
+
+ /* If an "aux" data provider was registered, ask it to capture
+ * whatever data it needs and give us an "aux_id" to it so that
+ * we can refer to it later when reporting.
+ */
+ if (g_aux_cb_alloc)
+ (g_aux_cb_alloc)(&pdata->aux_id);
+
+ return 0;
+}
+
+int git_win32__stack_compare(
+ git_win32__stack__raw_data *d1,
+ git_win32__stack__raw_data *d2)
+{
+ return memcmp(d1, d2, sizeof(*d1));
+}
+
+int git_win32__stack_format(
+ char *pbuf, int buf_len,
+ const git_win32__stack__raw_data *pdata,
+ const char *prefix, const char *suffix)
+{
+#define MY_MAX_FILENAME 255
+
+ /* SYMBOL_INFO has char FileName[1] at the end. The docs say to
+ * to malloc it with extra space for your desired max filename.
+ */
+ struct {
+ SYMBOL_INFO symbol;
+ char extra[MY_MAX_FILENAME + 1];
+ } s;
+
+ IMAGEHLP_LINE64 line;
+ int buf_used = 0;
+ unsigned int k;
+ char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */
+ int detail_len;
+
+ if (!g_win32_stack_initialized) {
+ giterr_set(GITERR_INVALID, "git_win32_stack not initialized.");
+ return GIT_ERROR;
+ }
+
+ if (!prefix)
+ prefix = "\t";
+ if (!suffix)
+ suffix = "\n";
+
+ memset(pbuf, 0, buf_len);
+
+ memset(&s, 0, sizeof(s));
+ s.symbol.MaxNameLen = MY_MAX_FILENAME;
+ s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
+
+ memset(&line, 0, sizeof(line));
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+ for (k=0; k < pdata->nr_frames; k++) {
+ DWORD64 frame_k = (DWORD64)pdata->frames[k];
+ DWORD dwUnused;
+
+ if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) &&
+ SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) {
+ const char *pslash;
+ const char *pfile;
+
+ pslash = strrchr(line.FileName, '\\');
+ pfile = ((pslash) ? (pslash+1) : line.FileName);
+ p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s",
+ prefix, pfile, line.LineNumber, s.symbol.Name, suffix);
+ } else {
+ /* This happens when we cross into another module.
+ * For example, in CLAR tests, this is typically
+ * the CRT startup code. Just print an unknown
+ * frame and continue.
+ */
+ p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix);
+ }
+ detail_len = strlen(detail);
+
+ if (buf_len < (buf_used + detail_len + 1)) {
+ /* we don't have room for this frame in the buffer, so just stop. */
+ break;
+ }
+
+ memcpy(&pbuf[buf_used], detail, detail_len);
+ buf_used += detail_len;
+ }
+
+ /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle
+ * allocs that occur before the aux callbacks were registered.
+ */
+ if (pdata->aux_id > 0) {
+ p_snprintf(detail, sizeof(detail), "%saux_id: %d%s",
+ prefix, pdata->aux_id, suffix);
+ detail_len = strlen(detail);
+ if ((buf_used + detail_len + 1) < buf_len) {
+ memcpy(&pbuf[buf_used], detail, detail_len);
+ buf_used += detail_len;
+ }
+
+ /* If an "aux" data provider is still registered, ask it to append its detailed
+ * data to the end of ours using the "aux_id" it gave us when this de-duped
+ * item was created.
+ */
+ if (g_aux_cb_lookup)
+ (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1));
+ }
+
+ return GIT_OK;
+}
+
+int git_win32__stack(
+ char * pbuf, int buf_len,
+ int skip,
+ const char *prefix, const char *suffix)
+{
+ git_win32__stack__raw_data data;
+ int error;
+
+ if ((error = git_win32__stack_capture(&data, skip)) < 0)
+ return error;
+ if ((error = git_win32__stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0)
+ return error;
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_w32_stack_h__
+#define INCLUDE_w32_stack_h__
+
+#if defined(GIT_MSVC_CRTDBG)
+
+/**
+ * This type defines a callback to be used to augment a C stacktrace
+ * with "aux" data. This can be used, for example, to allow LibGit2Sharp
+ * (or other interpreted consumer libraries) to give us C# stacktrace
+ * data for the PInvoke.
+ *
+ * This callback will be called during crtdbg-instrumented allocs.
+ *
+ * @param aux_id [out] A returned "aux_id" representing a unique
+ * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved
+ * to mean no aux stacktrace data.
+ */
+typedef void (*git_win32__stack__aux_cb_alloc)(unsigned int *aux_id);
+
+/**
+ * This type defines a callback to be used to augment the output of
+ * a stacktrace. This will be used to request the C# layer format
+ * the C# stacktrace associated with "aux_id" into the provided
+ * buffer.
+ *
+ * This callback will be called during leak reporting.
+ *
+ * @param aux_id The "aux_id" key associated with a stacktrace.
+ * @param aux_msg A buffer where a formatted message should be written.
+ * @param aux_msg_len The size of the buffer.
+ */
+typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len);
+
+/**
+ * Register an "aux" data provider to augment our C stacktrace data.
+ *
+ * This can be used, for example, to allow LibGit2Sharp (or other
+ * interpreted consumer libraries) to give us the C# stacktrace of
+ * the PInvoke.
+ *
+ * If you choose to use this feature, it should be registered during
+ * initialization and not changed for the duration of the process.
+ */
+GIT_EXTERN(int) git_win32__stack__set_aux_cb(
+ git_win32__stack__aux_cb_alloc cb_alloc,
+ git_win32__stack__aux_cb_lookup cb_lookup);
+
+/**
+ * Maximum number of stackframes to record for a
+ * single stacktrace.
+ */
+#define GIT_WIN32__STACK__MAX_FRAMES 30
+
+/**
+ * Wrapper containing the raw unprocessed stackframe
+ * data for a single stacktrace and any "aux_id".
+ *
+ * I put the aux_id first so leaks will be sorted by it.
+ * So, for example, if a specific callstack in C# leaks
+ * a repo handle, all of the pointers within the associated
+ * repo pointer will be grouped together.
+ */
+typedef struct {
+ unsigned int aux_id;
+ unsigned int nr_frames;
+ void *frames[GIT_WIN32__STACK__MAX_FRAMES];
+} git_win32__stack__raw_data;
+
+
+/**
+ * Load symbol table data. This should be done in the primary
+ * thread at startup (under a lock if there are other threads
+ * active).
+ */
+void git_win32__stack_init(void);
+
+/**
+ * Cleanup symbol table data. This should be done in the
+ * primary thead at shutdown (under a lock if there are other
+ * threads active).
+ */
+void git_win32__stack_cleanup(void);
+
+
+/**
+ * Capture raw stack trace data for the current process/thread.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ */
+int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip);
+
+/**
+ * Compare 2 raw stacktraces with the usual -1,0,+1 result.
+ * This includes any "aux_id" values in the comparison, so that
+ * our de-dup is also "aux" context relative.
+ */
+int git_win32__stack_compare(
+ git_win32__stack__raw_data *d1,
+ git_win32__stack__raw_data *d2);
+
+/**
+ * Format raw stacktrace data into buffer WITHOUT using any mallocs.
+ *
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+int git_win32__stack_format(
+ char *pbuf, int buf_len,
+ const git_win32__stack__raw_data *pdata,
+ const char *prefix, const char *suffix);
+
+/**
+ * Convenience routine to capture and format stacktrace into
+ * a buffer WITHOUT using any mallocs. This is primarily a
+ * wrapper for testing.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+int git_win32__stack(
+ char * pbuf, int buf_len,
+ int skip,
+ const char *prefix, const char *suffix);
+
+#endif /* GIT_MSVC_CRTDBG */
+#endif /* INCLUDE_w32_stack_h__ */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "w32_util.h"
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src)
+{
+ static const wchar_t suffix[] = L"\\*";
+ int len = git_win32_path_from_utf8(dest, src);
+
+ /* Ensure the path was converted */
+ if (len < 0)
+ return false;
+
+ /* Ensure that the path does not end with a trailing slash,
+ * because we're about to add one. Don't rely our trim_end
+ * helper, because we want to remove the backslash even for
+ * drive letter paths, in this case. */
+ if (len > 0 &&
+ (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) {
+ dest[len - 1] = L'\0';
+ len--;
+ }
+
+ /* Ensure we have enough room to add the suffix */
+ if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix))
+ return false;
+
+ wcscat(dest, suffix);
+ return true;
+}
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__set_hidden(const char *path, bool hidden)
+{
+ git_win32_path buf;
+ DWORD attrs, newattrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ if (hidden)
+ newattrs = attrs | FILE_ATTRIBUTE_HIDDEN;
+ else
+ newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN;
+
+ if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) {
+ giterr_set(GITERR_OS, "Failed to %s hidden bit for '%s'",
+ hidden ? "set" : "unset", path);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_win32__hidden(bool *out, const char *path)
+{
+ git_win32_path buf;
+ DWORD attrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false;
+ return 0;
+}
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len)
+{
+ while (1) {
+ if (!len || str[len - 1] != L'\\')
+ break;
+
+ /* Don't trim backslashes from drive letter paths, which
+ * are 3 characters long and of the form C:\, D:\, etc. */
+ if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+ break;
+
+ len--;
+ }
+
+ str[len] = L'\0';
+
+ return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len)
+{
+ static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
+ static const wchar_t nt_prefix[] = L"\\\\?\\";
+ static const wchar_t unc_prefix[] = L"UNC\\";
+ size_t to_advance = 0;
+
+ /* "\??\" -- DOS Devices prefix */
+ if (len >= CONST_STRLEN(dosdevices_prefix) &&
+ !wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
+ to_advance += CONST_STRLEN(dosdevices_prefix);
+ len -= CONST_STRLEN(dosdevices_prefix);
+ }
+ /* "\\?\" -- NT namespace prefix */
+ else if (len >= CONST_STRLEN(nt_prefix) &&
+ !wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
+ to_advance += CONST_STRLEN(nt_prefix);
+ len -= CONST_STRLEN(nt_prefix);
+ }
+
+ /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+ if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
+ !wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
+ to_advance += CONST_STRLEN(unc_prefix);
+ len -= CONST_STRLEN(unc_prefix);
+ }
+
+ if (to_advance) {
+ memmove(str, str + to_advance, len * sizeof(wchar_t));
+ str[len] = L'\0';
+ }
+
+ return git_win32__path_trim_end(str, len);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_w32_util_h__
+#define INCLUDE_w32_util_h__
+
+#include "utf-conv.h"
+#include "posix.h"
+#include "path_w32.h"
+
+/*
+
+#include "common.h"
+#include "path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
+*/
+
+
+GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
+{
+ return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
+}
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set
+ * or unset.
+ *
+ * @param path The path that should receive the +H bit.
+ * @param hidden true to set +H, false to unset it
+ * @return 0 on success; -1 on failure
+ */
+extern int git_win32__set_hidden(const char *path, bool hidden);
+
+/**
+ * Determines if the given file or folder has the hidden attribute set.
+ * @param hidden pointer to store hidden value
+ * @param path The path that should be queried for hiddenness.
+ * @return 0 on success or an error code.
+ */
+extern int git_win32__hidden(bool *hidden, const char *path);
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len);
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
+
+/**
+ * Converts a FILETIME structure to a struct timespec.
+ *
+ * @param FILETIME A pointer to a FILETIME
+ * @param ts A pointer to the timespec structure to fill in
+ */
+GIT_INLINE(void) git_win32__filetime_to_timespec(
+ const FILETIME *ft,
+ struct timespec *ts)
+{
+ long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
+ winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
+ ts->tv_sec = (time_t)(winTime / 10000000);
+#ifdef GIT_USE_NSEC
+ ts->tv_nsec = (winTime % 10000000) * 100;
+#else
+ ts->tv_nsec = 0;
+#endif
+}
+
+GIT_INLINE(void) git_win32__timeval_to_filetime(
+ FILETIME *ft, const struct p_timeval tv)
+{
+ long long ticks = (tv.tv_sec * 10000000LL) +
+ (tv.tv_usec * 10LL) + 116444736000000000LL;
+
+ ft->dwHighDateTime = ((ticks >> 32) & 0xffffffffLL);
+ ft->dwLowDateTime = (ticks & 0xffffffffLL);
+}
+
+GIT_INLINE(void) git_win32__stat_init(
+ struct stat *st,
+ DWORD dwFileAttributes,
+ DWORD nFileSizeHigh,
+ DWORD nFileSizeLow,
+ FILETIME ftCreationTime,
+ FILETIME ftLastAccessTime,
+ FILETIME ftLastWriteTime)
+{
+ mode_t mode = S_IREAD;
+
+ memset(st, 0, sizeof(struct stat));
+
+ if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ mode |= S_IFDIR;
+ else
+ mode |= S_IFREG;
+
+ if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0)
+ mode |= S_IWRITE;
+
+ st->st_ino = 0;
+ st->st_gid = 0;
+ st->st_uid = 0;
+ st->st_nlink = 1;
+ st->st_mode = mode;
+ st->st_size = ((git_off_t)nFileSizeHigh << 32) + nFileSizeLow;
+ st->st_dev = _getdrive() - 1;
+ st->st_rdev = st->st_dev;
+ git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim));
+ git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim));
+ git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim));
+}
+
+GIT_INLINE(void) git_win32__file_information_to_stat(
+ struct stat *st,
+ const BY_HANDLE_FILE_INFORMATION *fileinfo)
+{
+ git_win32__stat_init(st,
+ fileinfo->dwFileAttributes,
+ fileinfo->nFileSizeHigh,
+ fileinfo->nFileSizeLow,
+ fileinfo->ftCreationTime,
+ fileinfo->ftLastAccessTime,
+ fileinfo->ftLastWriteTime);
+}
+
+GIT_INLINE(int) git_win32__file_attribute_to_stat(
+ struct stat *st,
+ const WIN32_FILE_ATTRIBUTE_DATA *attrdata,
+ const wchar_t *path)
+{
+ git_win32__stat_init(st,
+ attrdata->dwFileAttributes,
+ attrdata->nFileSizeHigh,
+ attrdata->nFileSizeLow,
+ attrdata->ftCreationTime,
+ attrdata->ftLastAccessTime,
+ attrdata->ftLastWriteTime);
+
+ if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) {
+ git_win32_path target;
+
+ if (git_win32_path_readlink_w(target, path) >= 0) {
+ st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK;
+
+ /* st_size gets the UTF-8 length of the target name, in bytes,
+ * not counting the NULL terminator */
+ if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) {
+ giterr_set(GITERR_OS, "Could not convert reparse point name for '%ls'", path);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_win32_compat__
+#define INCLUDE_win32_compat__
+
+#include <stdint.h>
+#include <time.h>
+#include <wchar.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+typedef long suseconds_t;
+
+struct p_timeval {
+ time_t tv_sec;
+ suseconds_t tv_usec;
+};
+
+struct p_timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+
+#define timespec p_timespec
+
+struct p_stat {
+ _dev_t st_dev;
+ _ino_t st_ino;
+ mode_t st_mode;
+ short st_nlink;
+ short st_uid;
+ short st_gid;
+ _dev_t st_rdev;
+ __int64 st_size;
+ struct timespec st_atim;
+ struct timespec st_mtim;
+ struct timespec st_ctim;
+#define st_atime st_atim.tv_sec
+#define st_mtime st_mtim.tv_sec
+#define st_ctime st_ctim.tv_sec
+#define st_atime_nsec st_atim.tv_nsec
+#define st_mtime_nsec st_mtim.tv_nsec
+#define st_ctime_nsec st_ctim.tv_nsec
+};
+
+#define stat p_stat
+
+#endif /* INCLUDE_win32_compat__ */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "../util.h"
+
+#if !defined(XDIFF_H)
+#define XDIFF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* #ifdef __cplusplus */
+
+
+#define XDF_NEED_MINIMAL (1 << 1)
+#define XDF_IGNORE_WHITESPACE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
+
+#define XDF_PATIENCE_DIFF (1 << 5)
+#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
+#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
+
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
+
+#define XDL_EMIT_FUNCNAMES (1 << 0)
+#define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
+
+#define XDL_MMB_READONLY (1 << 0)
+
+#define XDL_MMF_ATOMIC (1 << 0)
+
+#define XDL_BDOP_INS 1
+#define XDL_BDOP_CPY 2
+#define XDL_BDOP_INSB 3
+
+/* merge simplification levels */
+#define XDL_MERGE_MINIMAL 0
+#define XDL_MERGE_EAGER 1
+#define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
+
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR_UNION 3
+
+/* merge output styles */
+#define XDL_MERGE_DIFF3 1
+
+typedef struct s_mmfile {
+ char *ptr;
+ size_t size;
+} mmfile_t;
+
+typedef struct s_mmbuffer {
+ char *ptr;
+ size_t size;
+} mmbuffer_t;
+
+typedef struct s_xpparam {
+ unsigned long flags;
+} xpparam_t;
+
+typedef struct s_xdemitcb {
+ void *priv;
+ int (*outf)(void *, mmbuffer_t *, int);
+} xdemitcb_t;
+
+typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+
+typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a,
+ long start_b, long count_b,
+ void *cb_data);
+
+typedef struct s_xdemitconf {
+ long ctxlen;
+ long interhunkctxlen;
+ unsigned long flags;
+ find_func_t find_func;
+ void *find_func_priv;
+ xdl_emit_hunk_consume_func_t hunk_func;
+} xdemitconf_t;
+
+typedef struct s_bdiffparam {
+ long bsize;
+} bdiffparam_t;
+
+
+#define xdl_malloc(x) git__malloc(x)
+#define xdl_free(ptr) git__free(ptr)
+#define xdl_realloc(ptr,x) git__realloc(ptr,x)
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size);
+long xdl_mmfile_size(mmfile_t *mmf);
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdemitconf_t const *xecfg, xdemitcb_t *ecb);
+
+typedef struct s_xmparam {
+ xpparam_t xpp;
+ int marker_size;
+ int level;
+ int favor;
+ int style;
+ const char *ancestor; /* label for orig */
+ const char *file1; /* label for mf1 */
+ const char *file2; /* label for mf2 */
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+ xmparam_t const *xmp, mmbuffer_t *result);
+
+#ifdef __cplusplus
+}
+#endif /* #ifdef __cplusplus */
+
+#endif /* #if !defined(XDIFF_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+#include "common.h"
+#include "integer.h"
+
+
+#define XDL_MAX_COST_MIN 256
+#define XDL_HEUR_MIN_COST 256
+#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1)
+#define XDL_SNAKE_CNT 20
+#define XDL_K_HEUR 4
+
+
+
+typedef struct s_xdpsplit {
+ long i1, i2;
+ int min_lo, min_hi;
+} xdpsplit_t;
+
+
+
+
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+ unsigned long const *ha2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+ xdalgoenv_t *xenv);
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
+
+
+
+
+
+/*
+ * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers.
+ * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both
+ * the forward diagonal starting from (off1, off2) and the backward diagonal
+ * starting from (lim1, lim2). If the K values on the same diagonal crosses
+ * returns the furthest point of reach. We might end up having to expensive
+ * cases using this algorithm is full, so a little bit of heuristic is needed
+ * to cut the search and to return a suboptimal point.
+ */
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+ unsigned long const *ha2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+ xdalgoenv_t *xenv) {
+ long dmin = off1 - lim2, dmax = lim1 - off2;
+ long fmid = off1 - off2, bmid = lim1 - lim2;
+ long odd = (fmid - bmid) & 1;
+ long fmin = fmid, fmax = fmid;
+ long bmin = bmid, bmax = bmid;
+ long ec, d, i1, i2, prev1, best, dd, v, k;
+
+ /*
+ * Set initial diagonal values for both forward and backward path.
+ */
+ kvdf[fmid] = off1;
+ kvdb[bmid] = lim1;
+
+ for (ec = 1;; ec++) {
+ int got_snake = 0;
+
+ /*
+ * We need to extent the diagonal "domain" by one. If the next
+ * values exits the box boundaries we need to change it in the
+ * opposite direction because (max - min) must be a power of two.
+ * Also we initialize the external K value to -1 so that we can
+ * avoid extra conditions check inside the core loop.
+ */
+ if (fmin > dmin)
+ kvdf[--fmin - 1] = -1;
+ else
+ ++fmin;
+ if (fmax < dmax)
+ kvdf[++fmax + 1] = -1;
+ else
+ --fmax;
+
+ for (d = fmax; d >= fmin; d -= 2) {
+ if (kvdf[d - 1] >= kvdf[d + 1])
+ i1 = kvdf[d - 1] + 1;
+ else
+ i1 = kvdf[d + 1];
+ prev1 = i1;
+ i2 = i1 - d;
+ for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++);
+ if (i1 - prev1 > xenv->snake_cnt)
+ got_snake = 1;
+ kvdf[d] = i1;
+ if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) {
+ spl->i1 = i1;
+ spl->i2 = i2;
+ spl->min_lo = spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ /*
+ * We need to extent the diagonal "domain" by one. If the next
+ * values exits the box boundaries we need to change it in the
+ * opposite direction because (max - min) must be a power of two.
+ * Also we initialize the external K value to -1 so that we can
+ * avoid extra conditions check inside the core loop.
+ */
+ if (bmin > dmin)
+ kvdb[--bmin - 1] = XDL_LINE_MAX;
+ else
+ ++bmin;
+ if (bmax < dmax)
+ kvdb[++bmax + 1] = XDL_LINE_MAX;
+ else
+ --bmax;
+
+ for (d = bmax; d >= bmin; d -= 2) {
+ if (kvdb[d - 1] < kvdb[d + 1])
+ i1 = kvdb[d - 1];
+ else
+ i1 = kvdb[d + 1] - 1;
+ prev1 = i1;
+ i2 = i1 - d;
+ for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--);
+ if (prev1 - i1 > xenv->snake_cnt)
+ got_snake = 1;
+ kvdb[d] = i1;
+ if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) {
+ spl->i1 = i1;
+ spl->i2 = i2;
+ spl->min_lo = spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ if (need_min)
+ continue;
+
+ /*
+ * If the edit cost is above the heuristic trigger and if
+ * we got a good snake, we sample current diagonals to see
+ * if some of the, have reached an "interesting" path. Our
+ * measure is a function of the distance from the diagonal
+ * corner (i1 + i2) penalized with the distance from the
+ * mid diagonal itself. If this value is above the current
+ * edit cost times a magic factor (XDL_K_HEUR) we consider
+ * it interesting.
+ */
+ if (got_snake && ec > xenv->heur_min) {
+ for (best = 0, d = fmax; d >= fmin; d -= 2) {
+ dd = d > fmid ? d - fmid: fmid - d;
+ i1 = kvdf[d];
+ i2 = i1 - d;
+ v = (i1 - off1) + (i2 - off2) - dd;
+
+ if (v > XDL_K_HEUR * ec && v > best &&
+ off1 + xenv->snake_cnt <= i1 && i1 < lim1 &&
+ off2 + xenv->snake_cnt <= i2 && i2 < lim2) {
+ for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++)
+ if (k == xenv->snake_cnt) {
+ best = v;
+ spl->i1 = i1;
+ spl->i2 = i2;
+ break;
+ }
+ }
+ }
+ if (best > 0) {
+ spl->min_lo = 1;
+ spl->min_hi = 0;
+ return ec;
+ }
+
+ for (best = 0, d = bmax; d >= bmin; d -= 2) {
+ dd = d > bmid ? d - bmid: bmid - d;
+ i1 = kvdb[d];
+ i2 = i1 - d;
+ v = (lim1 - i1) + (lim2 - i2) - dd;
+
+ if (v > XDL_K_HEUR * ec && v > best &&
+ off1 < i1 && i1 <= lim1 - xenv->snake_cnt &&
+ off2 < i2 && i2 <= lim2 - xenv->snake_cnt) {
+ for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++)
+ if (k == xenv->snake_cnt - 1) {
+ best = v;
+ spl->i1 = i1;
+ spl->i2 = i2;
+ break;
+ }
+ }
+ }
+ if (best > 0) {
+ spl->min_lo = 0;
+ spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ /*
+ * Enough is enough. We spent too much time here and now we collect
+ * the furthest reaching path using the (i1 + i2) measure.
+ */
+ if (ec >= xenv->mxcost) {
+ long fbest, fbest1, bbest, bbest1;
+
+ fbest = fbest1 = -1;
+ for (d = fmax; d >= fmin; d -= 2) {
+ i1 = XDL_MIN(kvdf[d], lim1);
+ i2 = i1 - d;
+ if (lim2 < i2)
+ i1 = lim2 + d, i2 = lim2;
+ if (fbest < i1 + i2) {
+ fbest = i1 + i2;
+ fbest1 = i1;
+ }
+ }
+
+ bbest = bbest1 = XDL_LINE_MAX;
+ for (d = bmax; d >= bmin; d -= 2) {
+ i1 = XDL_MAX(off1, kvdb[d]);
+ i2 = i1 - d;
+ if (i2 < off2)
+ i1 = off2 + d, i2 = off2;
+ if (i1 + i2 < bbest) {
+ bbest = i1 + i2;
+ bbest1 = i1;
+ }
+ }
+
+ if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) {
+ spl->i1 = fbest1;
+ spl->i2 = fbest - fbest1;
+ spl->min_lo = 1;
+ spl->min_hi = 0;
+ } else {
+ spl->i1 = bbest1;
+ spl->i2 = bbest - bbest1;
+ spl->min_lo = 0;
+ spl->min_hi = 1;
+ }
+ return ec;
+ }
+ }
+}
+
+
+/*
+ * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling
+ * the box splitting function. Note that the real job (marking changed lines)
+ * is done in the two boundary reaching checks.
+ */
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+ diffdata_t *dd2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
+ unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+
+ /*
+ * Shrink the box by walking through each diagonal snake (SW and NE).
+ */
+ for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++);
+ for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--);
+
+ /*
+ * If one dimension is empty, then all records on the other one must
+ * be obviously changed.
+ */
+ if (off1 == lim1) {
+ char *rchg2 = dd2->rchg;
+ long *rindex2 = dd2->rindex;
+
+ for (; off2 < lim2; off2++)
+ rchg2[rindex2[off2]] = 1;
+ } else if (off2 == lim2) {
+ char *rchg1 = dd1->rchg;
+ long *rindex1 = dd1->rindex;
+
+ for (; off1 < lim1; off1++)
+ rchg1[rindex1[off1]] = 1;
+ } else {
+ xdpsplit_t spl;
+ spl.i1 = spl.i2 = 0;
+
+ /*
+ * Divide ...
+ */
+ if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+ need_min, &spl, xenv) < 0) {
+
+ return -1;
+ }
+
+ /*
+ * ... et Impera.
+ */
+ if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2,
+ kvdf, kvdb, spl.min_lo, xenv) < 0 ||
+ xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2,
+ kvdf, kvdb, spl.min_hi, xenv) < 0) {
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe) {
+ size_t ndiags, allocsize;
+ long *kvd, *kvdf, *kvdb;
+ xdalgoenv_t xenv;
+ diffdata_t dd1, dd2;
+
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF)
+ return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+ return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+
+ if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
+
+ return -1;
+ }
+
+ /*
+ * Allocate and setup K vectors to be used by the differential algorithm.
+ * One is to store the forward path and one to store the backward path.
+ */
+ GITERR_CHECK_ALLOC_ADD3(&ndiags, xe->xdf1.nreff, xe->xdf2.nreff, 3);
+ GITERR_CHECK_ALLOC_MULTIPLY(&allocsize, ndiags, 2);
+ GITERR_CHECK_ALLOC_ADD(&allocsize, allocsize, 2);
+ GITERR_CHECK_ALLOC_MULTIPLY(&allocsize, allocsize, sizeof(long));
+
+ if (!(kvd = (long *) xdl_malloc(allocsize))) {
+ xdl_free_env(xe);
+ return -1;
+ }
+ kvdf = kvd;
+ kvdb = kvdf + ndiags;
+ kvdf += xe->xdf2.nreff + 1;
+ kvdb += xe->xdf2.nreff + 1;
+
+ xenv.mxcost = xdl_bogosqrt(ndiags);
+ if (xenv.mxcost < XDL_MAX_COST_MIN)
+ xenv.mxcost = XDL_MAX_COST_MIN;
+ xenv.snake_cnt = XDL_SNAKE_CNT;
+ xenv.heur_min = XDL_HEUR_MIN_COST;
+
+ dd1.nrec = xe->xdf1.nreff;
+ dd1.ha = xe->xdf1.ha;
+ dd1.rchg = xe->xdf1.rchg;
+ dd1.rindex = xe->xdf1.rindex;
+ dd2.nrec = xe->xdf2.nreff;
+ dd2.ha = xe->xdf2.ha;
+ dd2.rchg = xe->xdf2.rchg;
+ dd2.rindex = xe->xdf2.rindex;
+
+ if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+ kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) {
+
+ xdl_free(kvd);
+ xdl_free_env(xe);
+ return -1;
+ }
+
+ xdl_free(kvd);
+
+ return 0;
+}
+
+
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) {
+ xdchange_t *xch;
+
+ if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t))))
+ return NULL;
+
+ xch->next = xscr;
+ xch->i1 = i1;
+ xch->i2 = i2;
+ xch->chg1 = chg1;
+ xch->chg2 = chg2;
+ xch->ignore = 0;
+
+ return xch;
+}
+
+
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
+ long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
+ char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
+ xrecord_t **recs = xdf->recs;
+
+ /*
+ * This is the same of what GNU diff does. Move back and forward
+ * change groups for a consistent and pretty diff output. This also
+ * helps in finding joinable change groups and reduce the diff size.
+ */
+ for (ix = ixo = 0;;) {
+ /*
+ * Find the first changed line in the to-be-compacted file.
+ * We need to keep track of both indexes, so if we find a
+ * changed lines group on the other file, while scanning the
+ * to-be-compacted file, we need to skip it properly. Note
+ * that loops that are testing for changed lines on rchg* do
+ * not need index bounding since the array is prepared with
+ * a zero at position -1 and N.
+ */
+ for (; ix < nrec && !rchg[ix]; ix++)
+ while (rchgo[ixo++]);
+ if (ix == nrec)
+ break;
+
+ /*
+ * Record the start of a changed-group in the to-be-compacted file
+ * and find the end of it, on both to-be-compacted and other file
+ * indexes (ix and ixo).
+ */
+ ixs = ix;
+ for (ix++; rchg[ix]; ix++);
+ for (; rchgo[ixo]; ixo++);
+
+ do {
+ grpsiz = ix - ixs;
+
+ /*
+ * If the line before the current change group, is equal to
+ * the last line of the current change group, shift backward
+ * the group.
+ */
+ while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha &&
+ xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) {
+ rchg[--ixs] = 1;
+ rchg[--ix] = 0;
+
+ /*
+ * This change might have joined two change groups,
+ * so we try to take this scenario in account by moving
+ * the start index accordingly (and so the other-file
+ * end-of-group index).
+ */
+ for (; rchg[ixs - 1]; ixs--);
+ while (rchgo[--ixo]);
+ }
+
+ /*
+ * Record the end-of-group position in case we are matched
+ * with a group of changes in the other file (that is, the
+ * change record before the end-of-group index in the other
+ * file is set).
+ */
+ ixref = rchgo[ixo - 1] ? ix: nrec;
+
+ /*
+ * If the first line of the current change group, is equal to
+ * the line next of the current change group, shift forward
+ * the group.
+ */
+ while (ix < nrec && recs[ixs]->ha == recs[ix]->ha &&
+ xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) {
+ rchg[ixs++] = 0;
+ rchg[ix++] = 1;
+
+ /*
+ * This change might have joined two change groups,
+ * so we try to take this scenario in account by moving
+ * the start index accordingly (and so the other-file
+ * end-of-group index). Keep tracking the reference
+ * index in case we are shifting together with a
+ * corresponding group of changes in the other file.
+ */
+ for (; rchg[ix]; ix++);
+ while (rchgo[++ixo])
+ ixref = ix;
+ }
+ } while (grpsiz != ix - ixs);
+
+ /*
+ * Try to move back the possibly merged group of changes, to match
+ * the recorded position in the other file.
+ */
+ while (ixref < ix) {
+ rchg[--ixs] = 1;
+ rchg[--ix] = 0;
+ while (rchgo[--ixo]);
+ }
+ }
+
+ return 0;
+}
+
+
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
+ xdchange_t *cscr = NULL, *xch;
+ char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+ long i1, i2, l1, l2;
+
+ /*
+ * Trivial. Collects "groups" of changes and creates an edit script.
+ */
+ for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+ if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
+ for (l1 = i1; rchg1[i1 - 1]; i1--);
+ for (l2 = i2; rchg2[i2 - 1]; i2--);
+
+ if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) {
+ xdl_free_script(cscr);
+ return -1;
+ }
+ cscr = xch;
+ }
+
+ *xscr = cscr;
+
+ return 0;
+}
+
+
+void xdl_free_script(xdchange_t *xscr) {
+ xdchange_t *xch;
+
+ while ((xch = xscr) != NULL) {
+ xscr = xscr->next;
+ xdl_free(xch);
+ }
+}
+
+static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch, *xche;
+
+ (void)xe;
+
+ for (xch = xscr; xch; xch = xche->next) {
+ xche = xdl_get_hunk(&xch, xecfg);
+ if (!xch)
+ break;
+ if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
+ xch->i2, xche->i2 + xche->chg2 - xch->i2,
+ ecb->priv) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
+{
+ xdchange_t *xch;
+
+ for (xch = xscr; xch; xch = xch->next) {
+ int ignore = 1;
+ xrecord_t **rec;
+ long i;
+
+ rec = &xe->xdf1.recs[xch->i1];
+ for (i = 0; i < xch->chg1 && ignore; i++)
+ ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+ rec = &xe->xdf2.recs[xch->i2];
+ for (i = 0; i < xch->chg2 && ignore; i++)
+ ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+ xch->ignore = ignore;
+ }
+}
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
+ xdchange_t *xscr;
+ xdfenv_t xe;
+ emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff;
+
+ if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
+
+ return -1;
+ }
+ if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe, &xscr) < 0) {
+
+ xdl_free_env(&xe);
+ return -1;
+ }
+ if (xscr) {
+ if (xpp->flags & XDF_IGNORE_BLANK_LINES)
+ xdl_mark_ignorable(xscr, &xe, xpp->flags);
+
+ if (ef(&xe, xscr, ecb, xecfg) < 0) {
+
+ xdl_free_script(xscr);
+ xdl_free_env(&xe);
+ return -1;
+ }
+ xdl_free_script(xscr);
+ }
+ xdl_free_env(&xe);
+
+ return 0;
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XDIFFI_H)
+#define XDIFFI_H
+
+
+typedef struct s_diffdata {
+ long nrec;
+ unsigned long const *ha;
+ long *rindex;
+ char *rchg;
+} diffdata_t;
+
+typedef struct s_xdalgoenv {
+ long mxcost;
+ long snake_cnt;
+ long heur_min;
+} xdalgoenv_t;
+
+typedef struct s_xdchange {
+ struct s_xdchange *next;
+ long i1, i2;
+ long chg1, chg2;
+ int ignore;
+} xdchange_t;
+
+
+
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+ diffdata_t *dd2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe);
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
+void xdl_free_script(xdchange_t *xscr);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
+int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
+
+#endif /* #if !defined(XDIFFI_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec);
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb);
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
+
+ *rec = xdf->recs[ri]->ptr;
+
+ return xdf->recs[ri]->size;
+}
+
+
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) {
+ long size, psize = (long)strlen(pre);
+ char const *rec;
+
+ size = xdl_get_rec(xdf, ri, &rec);
+ if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Starting at the passed change atom, find the latest change atom to be included
+ * inside the differential hunk according to the specified configuration.
+ * Also advance xscr if the first changes must be discarded.
+ */
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch, *xchp, *lxch;
+ long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
+ long max_ignorable = xecfg->ctxlen;
+ unsigned long ignored = 0; /* number of ignored blank lines */
+
+ /* remove ignorable changes that are too far before other changes */
+ for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
+ xch = xchp->next;
+
+ if (xch == NULL ||
+ xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable)
+ *xscr = xch;
+ }
+
+ if (*xscr == NULL)
+ return NULL;
+
+ lxch = *xscr;
+
+ for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) {
+ long distance = xch->i1 - (xchp->i1 + xchp->chg1);
+ if (distance > max_common)
+ break;
+
+ if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) {
+ lxch = xch;
+ ignored = 0;
+ } else if (distance < max_ignorable && xch->ignore) {
+ ignored += xch->chg2;
+ } else if (lxch != xchp &&
+ xch->i1 + ignored - (lxch->i1 + lxch->chg1) > (unsigned long)max_common) {
+ break;
+ } else if (!xch->ignore) {
+ lxch = xch;
+ ignored = 0;
+ } else {
+ ignored += xch->chg2;
+ }
+ }
+
+ return lxch;
+}
+
+
+static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
+{
+ (void)priv;
+
+ if (len > 0 &&
+ (isalpha((unsigned char)*rec) || /* identifier? */
+ *rec == '_' || /* also identifier? */
+ *rec == '$')) { /* identifiers from VMS and other esoterico */
+ if (len > sz)
+ len = sz;
+ while (0 < len && isspace((unsigned char)rec[len - 1]))
+ len--;
+ memcpy(buf, rec, len);
+ return len;
+ }
+ return -1;
+}
+
+static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
+ xdfile_t *xdf = &xe->xdf2;
+ const char *rchg = xdf->rchg;
+ long ix;
+
+ (void)xscr;
+ (void)xecfg;
+
+ for (ix = 0; ix < xdf->nrec; ix++) {
+ if (rchg[ix])
+ continue;
+ if (xdl_emit_record(xdf, ix, "", ecb))
+ return -1;
+ }
+ return 0;
+}
+
+struct func_line {
+ long len;
+ char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+ struct func_line *func_line, long start, long limit)
+{
+ find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+ long l, size, step = (start > limit) ? -1 : 1;
+ char *buf, dummy[1];
+
+ buf = func_line ? func_line->buf : dummy;
+ size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+ for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+ const char *rec;
+ long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+ long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
+ if (len >= 0) {
+ if (func_line)
+ func_line->len = len;
+ return l;
+ }
+ }
+ return -1;
+}
+
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
+ long s1, s2, e1, e2, lctx;
+ xdchange_t *xch, *xche;
+ long funclineprev = -1;
+ struct func_line func_line = { 0 };
+
+ if (xecfg->flags & XDL_EMIT_COMMON)
+ return xdl_emit_common(xe, xscr, ecb, xecfg);
+
+ for (xch = xscr; xch; xch = xche->next) {
+ xche = xdl_get_hunk(&xch, xecfg);
+ if (!xch)
+ break;
+
+ s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
+ s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+ if (fs1 < 0)
+ fs1 = 0;
+ if (fs1 < s1) {
+ s2 -= s1 - fs1;
+ s1 = fs1;
+ }
+ }
+
+ again:
+ lctx = xecfg->ctxlen;
+ lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
+ lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+
+ e1 = xche->i1 + xche->chg1 + lctx;
+ e2 = xche->i2 + xche->chg2 + lctx;
+
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fe1 = get_func_line(xe, xecfg, NULL,
+ xche->i1 + xche->chg1,
+ xe->xdf1.nrec);
+ if (fe1 < 0)
+ fe1 = xe->xdf1.nrec;
+ if (fe1 > e1) {
+ e2 += fe1 - e1;
+ e1 = fe1;
+ }
+
+ /*
+ * Overlap with next change? Then include it
+ * in the current hunk and start over to find
+ * its new end.
+ */
+ if (xche->next) {
+ long l = xche->next->i1;
+ if (l <= e1 ||
+ get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+ xche = xche->next;
+ goto again;
+ }
+ }
+ }
+
+ /*
+ * Emit current hunk header.
+ */
+
+ if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
+ get_func_line(xe, xecfg, &func_line,
+ s1 - 1, funclineprev);
+ funclineprev = s1 - 1;
+ }
+ if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
+ func_line.buf, func_line.len, ecb) < 0)
+ return -1;
+
+ /*
+ * Emit pre-context.
+ */
+ for (; s2 < xch->i2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+
+ for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
+ /*
+ * Merge previous with current change atom.
+ */
+ for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+
+ /*
+ * Removes lines from the first file.
+ */
+ for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++)
+ if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0)
+ return -1;
+
+ /*
+ * Adds lines from the second file.
+ */
+ for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0)
+ return -1;
+
+ if (xch == xche)
+ break;
+ s1 = xch->i1 + xch->chg1;
+ s2 = xch->i2 + xch->chg2;
+ }
+
+ /*
+ * Emit post-context.
+ */
+ for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XEMIT_H)
+#define XEMIT_H
+
+
+typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+
+
+
+#endif /* #if !defined(XEMIT_H) */
--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in JGit's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+#include "common.h"
+
+#define MAX_PTR UINT_MAX
+#define MAX_CNT UINT_MAX
+
+#define LINE_END(n) (line##n + count##n - 1)
+#define LINE_END_PTR(n) (*line##n + *count##n - 1)
+
+struct histindex {
+ struct record {
+ unsigned int ptr, cnt;
+ struct record *next;
+ } **records, /* an occurrence */
+ **line_map; /* map of line to record chain */
+ chastore_t rcha;
+ unsigned int *next_ptrs;
+ unsigned int table_bits,
+ records_size,
+ line_map_size;
+
+ unsigned int max_chain_length,
+ key_shift,
+ ptr_shift;
+
+ unsigned int cnt,
+ has_common;
+
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+struct region {
+ unsigned int begin1, end1;
+ unsigned int begin2, end2;
+};
+
+#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift])
+
+#define NEXT_PTR(index, ptr) \
+ (index->next_ptrs[(ptr) - index->ptr_shift])
+
+#define CNT(index, ptr) \
+ ((LINE_MAP(index, ptr))->cnt)
+
+#define REC(env, s, l) \
+ (env->xdf##s.recs[l - 1])
+
+static int cmp_recs(xpparam_t const *xpp,
+ xrecord_t *r1, xrecord_t *r2)
+{
+ return r1->ha == r2->ha &&
+ xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size,
+ xpp->flags);
+}
+
+#define CMP_ENV(xpp, env, s1, l1, s2, l2) \
+ (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2)))
+
+#define CMP(i, s1, l1, s2, l2) \
+ (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2)))
+
+#define TABLE_HASH(index, side, line) \
+ XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+
+static int scanA(struct histindex *index, unsigned int line1, unsigned int count1)
+{
+ unsigned int ptr;
+ unsigned int tbl_idx;
+ unsigned int chain_len;
+ struct record **rec_chain, *rec;
+
+ for (ptr = LINE_END(1); line1 <= ptr; ptr--) {
+ tbl_idx = TABLE_HASH(index, 1, ptr);
+ rec_chain = index->records + tbl_idx;
+ rec = *rec_chain;
+
+ chain_len = 0;
+ while (rec) {
+ if (CMP(index, 1, rec->ptr, 1, ptr)) {
+ /*
+ * ptr is identical to another element. Insert
+ * it onto the front of the existing element
+ * chain.
+ */
+ NEXT_PTR(index, ptr) = rec->ptr;
+ rec->ptr = ptr;
+ /* cap rec->cnt at MAX_CNT */
+ rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
+ LINE_MAP(index, ptr) = rec;
+ goto continue_scan;
+ }
+
+ rec = rec->next;
+ chain_len++;
+ }
+
+ if (chain_len == index->max_chain_length)
+ return -1;
+
+ /*
+ * This is the first time we have ever seen this particular
+ * element in the sequence. Construct a new chain for it.
+ */
+ if (!(rec = xdl_cha_alloc(&index->rcha)))
+ return -1;
+ rec->ptr = ptr;
+ rec->cnt = 1;
+ rec->next = *rec_chain;
+ *rec_chain = rec;
+ LINE_MAP(index, ptr) = rec;
+
+continue_scan:
+ ; /* no op */
+ }
+
+ return 0;
+}
+
+static int try_lcs(
+ struct histindex *index, struct region *lcs, unsigned int b_ptr,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ unsigned int b_next = b_ptr + 1;
+ struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)];
+ unsigned int as, ae, bs, be, np, rc;
+ int should_break;
+
+ for (; rec; rec = rec->next) {
+ if (rec->cnt > index->cnt) {
+ if (!index->has_common)
+ index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr);
+ continue;
+ }
+
+ as = rec->ptr;
+ if (!CMP(index, 1, as, 2, b_ptr))
+ continue;
+
+ index->has_common = 1;
+ for (;;) {
+ should_break = 0;
+ np = NEXT_PTR(index, as);
+ bs = b_ptr;
+ ae = as;
+ be = bs;
+ rc = rec->cnt;
+
+ while (line1 < as && line2 < bs
+ && CMP(index, 1, as - 1, 2, bs - 1)) {
+ as--;
+ bs--;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, as));
+ }
+ while (ae < LINE_END(1) && be < LINE_END(2)
+ && CMP(index, 1, ae + 1, 2, be + 1)) {
+ ae++;
+ be++;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, ae));
+ }
+
+ if (b_next <= be)
+ b_next = be + 1;
+ if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) {
+ lcs->begin1 = as;
+ lcs->begin2 = bs;
+ lcs->end1 = ae;
+ lcs->end2 = be;
+ index->cnt = rc;
+ }
+
+ if (np == 0)
+ break;
+
+ while (np <= ae) {
+ np = NEXT_PTR(index, np);
+ if (np == 0) {
+ should_break = 1;
+ break;
+ }
+ }
+
+ if (should_break)
+ break;
+
+ as = np;
+ }
+ }
+ return b_next;
+}
+
+static int find_lcs(
+ struct histindex *index, struct region *lcs,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ unsigned int b_ptr;
+
+ if (scanA(index, line1, count1))
+ return -1;
+
+ index->cnt = index->max_chain_length + 1;
+
+ for (b_ptr = line2; b_ptr <= LINE_END(2); )
+ b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2);
+
+ return index->has_common && index->max_chain_length < index->cnt;
+}
+
+static int fall_back_to_classic_diff(struct histindex *index,
+ int line1, int count1, int line2, int count2)
+{
+ xpparam_t xpp;
+ xpp.flags = index->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
+
+ return xdl_fall_back_diff(index->env, &xpp,
+ line1, count1, line2, count2);
+}
+
+static int histogram_diff(
+ xpparam_t const *xpp, xdfenv_t *env,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ struct histindex index;
+ struct region lcs;
+ size_t sz;
+ int result = -1;
+
+ if (count1 <= 0 && count2 <= 0)
+ return 0;
+
+ if (LINE_END(1) >= MAX_PTR)
+ return -1;
+
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&index, 0, sizeof(index));
+
+ index.env = env;
+ index.xpp = xpp;
+
+ index.records = NULL;
+ index.line_map = NULL;
+ /* in case of early xdl_cha_free() */
+ index.rcha.head = NULL;
+
+ index.table_bits = xdl_hashbits(count1);
+ sz = index.records_size = 1 << index.table_bits;
+ GITERR_CHECK_ALLOC_MULTIPLY(&sz, sz, sizeof(struct record *));
+
+ if (!(index.records = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.records, 0, sz);
+
+ sz = index.line_map_size = count1;
+ sz *= sizeof(struct record *);
+ if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.line_map, 0, sz);
+
+ sz = index.line_map_size;
+ sz *= sizeof(unsigned int);
+ if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.next_ptrs, 0, sz);
+
+ /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+ if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
+ goto cleanup;
+
+ index.ptr_shift = line1;
+ index.max_chain_length = 64;
+
+ memset(&lcs, 0, sizeof(lcs));
+ if (find_lcs(&index, &lcs, line1, count1, line2, count2))
+ result = fall_back_to_classic_diff(&index, line1, count1, line2, count2);
+ else {
+ if (lcs.begin1 == 0 && lcs.begin2 == 0) {
+ while (count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while (count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ result = 0;
+ } else {
+ result = histogram_diff(xpp, env,
+ line1, lcs.begin1 - line1,
+ line2, lcs.begin2 - line2);
+ if (result)
+ goto cleanup;
+ result = histogram_diff(xpp, env,
+ lcs.end1 + 1, LINE_END(1) - lcs.end1,
+ lcs.end2 + 1, LINE_END(2) - lcs.end2);
+ if (result)
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ xdl_free(index.records);
+ xdl_free(index.line_map);
+ xdl_free(index.next_ptrs);
+ xdl_cha_free(&index.rcha);
+
+ return result;
+}
+
+int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ return histogram_diff(xpp, env,
+ env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
+ env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XINCLUDE_H)
+#define XINCLUDE_H
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef _WIN32
+#else
+#include <unistd.h>
+#endif
+
+#include "xmacros.h"
+#include "xdiff.h"
+#include "xtypes.h"
+#include "xutils.h"
+#include "xprepare.h"
+#include "xdiffi.h"
+#include "xemit.h"
+
+
+#endif /* #if !defined(XINCLUDE_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XMACROS_H)
+#define XMACROS_H
+
+
+
+
+#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
+#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
+#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
+#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+#define XDL_ISSPACE(c) (isspace((unsigned char)(c)))
+#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
+#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
+#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
+#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
+#define XDL_LE32_PUT(p, v) \
+do { \
+ unsigned char *__p = (unsigned char *) (p); \
+ *__p++ = (unsigned char) (v); \
+ *__p++ = (unsigned char) ((v) >> 8); \
+ *__p++ = (unsigned char) ((v) >> 16); \
+ *__p = (unsigned char) ((v) >> 24); \
+} while (0)
+#define XDL_LE32_GET(p, v) \
+do { \
+ unsigned char const *__p = (unsigned char const *) (p); \
+ (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \
+ ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \
+} while (0)
+
+
+#endif /* #if !defined(XMACROS_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+#include "common.h"
+
+typedef struct s_xdmerge {
+ struct s_xdmerge *next;
+ /*
+ * 0 = conflict,
+ * 1 = no conflict, take first,
+ * 2 = no conflict, take second.
+ * 3 = no conflict, take both.
+ */
+ int mode;
+ /*
+ * These point at the respective postimages. E.g. <i1,chg1> is
+ * how side #1 wants to change the common ancestor; if there is no
+ * overlap, lines before i1 in the postimage of side #1 appear
+ * in the merge result as a region touched by neither side.
+ */
+ long i1, i2;
+ long chg1, chg2;
+ /*
+ * These point at the preimage; of course there is just one
+ * preimage, that is from the shared common ancestor.
+ */
+ long i0;
+ long chg0;
+} xdmerge_t;
+
+static int xdl_append_merge(xdmerge_t **merge, int mode,
+ long i0, long chg0,
+ long i1, long chg1,
+ long i2, long chg2)
+{
+ xdmerge_t *m = *merge;
+ if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) {
+ if (mode != m->mode)
+ m->mode = 0;
+ m->chg0 = i0 + chg0 - m->i0;
+ m->chg1 = i1 + chg1 - m->i1;
+ m->chg2 = i2 + chg2 - m->i2;
+ } else {
+ m = xdl_malloc(sizeof(xdmerge_t));
+ if (!m)
+ return -1;
+ m->next = NULL;
+ m->mode = mode;
+ m->i0 = i0;
+ m->chg0 = chg0;
+ m->i1 = i1;
+ m->chg1 = chg1;
+ m->i2 = i2;
+ m->chg2 = chg2;
+ if (*merge)
+ (*merge)->next = m;
+ *merge = m;
+ }
+ return 0;
+}
+
+static int xdl_cleanup_merge(xdmerge_t *c)
+{
+ int count = 0;
+ xdmerge_t *next_c;
+
+ /* were there conflicts? */
+ for (; c; c = next_c) {
+ if (c->mode == 0)
+ count++;
+ next_c = c->next;
+ free(c);
+ }
+ return count;
+}
+
+static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
+ int line_count, long flags)
+{
+ int i;
+ xrecord_t **rec1 = xe1->xdf2.recs + i1;
+ xrecord_t **rec2 = xe2->xdf2.recs + i2;
+
+ for (i = 0; i < line_count; i++) {
+ int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
+ rec2[i]->ptr, rec2[i]->size, flags);
+ if (!result)
+ return -1;
+ }
+ return 0;
+}
+
+static int xdl_recs_copy_0(size_t *out, int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ xrecord_t **recs;
+ size_t size = 0;
+
+ *out = 0;
+
+ recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;
+
+ if (count < 1)
+ return 0;
+
+ for (i = 0; i < count; ) {
+ if (dest)
+ memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+
+ GITERR_CHECK_ALLOC_ADD(&size, size, recs[i++]->size);
+ }
+
+ if (add_nl) {
+ i = recs[count - 1]->size;
+ if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+ if (dest)
+ dest[size] = '\n';
+
+ GITERR_CHECK_ALLOC_ADD(&size, size, 1);
+ }
+ }
+
+ *out = size;
+ return 0;
+}
+
+static int xdl_recs_copy(size_t *out, xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ return xdl_recs_copy_0(out, 0, xe, i, count, add_nl, dest);
+}
+
+static int xdl_orig_copy(size_t *out, xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ return xdl_recs_copy_0(out, 1, xe, i, count, add_nl, dest);
+}
+
+static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1,
+ xdfenv_t *xe2, const char *name2,
+ const char *name3,
+ size_t size, int i, int style,
+ xdmerge_t *m, char *dest, int marker_size)
+{
+ int marker1_size = (name1 ? (int)strlen(name1) + 1 : 0);
+ int marker2_size = (name2 ? (int)strlen(name2) + 1 : 0);
+ int marker3_size = (name3 ? (int)strlen(name3) + 1 : 0);
+ size_t copied;
+
+ *out = 0;
+
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
+ /* Before conflicting part */
+ if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+
+ if (!dest) {
+ GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker1_size);
+ } else {
+ memset(dest + size, '<', marker_size);
+ size += marker_size;
+ if (marker1_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name1, marker1_size - 1);
+ size += marker1_size;
+ }
+ dest[size++] = '\n';
+ }
+
+ /* Postimage from side #1 */
+ if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, 1,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+
+ if (style == XDL_MERGE_DIFF3) {
+ /* Shared preimage */
+ if (!dest) {
+ GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker3_size);
+ } else {
+ memset(dest + size, '|', marker_size);
+ size += marker_size;
+ if (marker3_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name3, marker3_size - 1);
+ size += marker3_size;
+ }
+ dest[size++] = '\n';
+ }
+
+ if (xdl_orig_copy(&copied, xe1, m->i0, m->chg0, 1,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+ }
+
+ if (!dest) {
+ GITERR_CHECK_ALLOC_ADD3(&size, size, marker_size, 1);
+ } else {
+ memset(dest + size, '=', marker_size);
+ size += marker_size;
+ dest[size++] = '\n';
+ }
+
+ /* Postimage from side #2 */
+
+ if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 1,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+
+ if (!dest) {
+ GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker2_size);
+ } else {
+ memset(dest + size, '>', marker_size);
+ size += marker_size;
+ if (marker2_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name2, marker2_size - 1);
+ size += marker2_size;
+ }
+ dest[size++] = '\n';
+ }
+
+ *out = size;
+ return 0;
+}
+
+static int xdl_fill_merge_buffer(size_t *out,
+ xdfenv_t *xe1, const char *name1,
+ xdfenv_t *xe2, const char *name2,
+ const char *ancestor_name,
+ int favor,
+ xdmerge_t *m, char *dest, int style,
+ int marker_size)
+{
+ size_t size, copied;
+ int i;
+
+ *out = 0;
+
+ for (size = i = 0; m; m = m->next) {
+ if (favor && !m->mode)
+ m->mode = favor;
+
+ if (m->mode == 0) {
+ if (fill_conflict_hunk(&size, xe1, name1, xe2, name2,
+ ancestor_name,
+ size, i, style, m, dest,
+ marker_size) < 0)
+ return -1;
+ }
+ else if (m->mode & 3) {
+ /* Before conflicting part */
+ if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+
+ /* Postimage from side #1 */
+ if (m->mode & 1) {
+ if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, (m->mode & 2),
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+ }
+
+ /* Postimage from side #2 */
+ if (m->mode & 2) {
+ if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 0,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+ }
+ } else
+ continue;
+ i = m->i1 + m->chg1;
+ }
+
+ if (xdl_recs_copy(&copied, xe1, i, xe1->xdf2.nrec - i, 0,
+ dest ? dest + size : NULL) < 0)
+ return -1;
+ GITERR_CHECK_ALLOC_ADD(&size, size, copied);
+
+ *out = size;
+ return 0;
+}
+
+/*
+ * Sometimes, changes are not quite identical, but differ in only a few
+ * lines. Try hard to show only these few lines as conflicting.
+ */
+static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
+ xpparam_t const *xpp)
+{
+ for (; m; m = m->next) {
+ mmfile_t t1, t2;
+ xdfenv_t xe;
+ xdchange_t *xscr, *x;
+ int i1 = m->i1, i2 = m->i2;
+
+ /* let's handle just the conflicts */
+ if (m->mode)
+ continue;
+
+ /* no sense refining a conflict when one side is empty */
+ if (m->chg1 == 0 || m->chg2 == 0)
+ continue;
+
+ /*
+ * This probably does not work outside git, since
+ * we have a very simple mmfile structure.
+ */
+ t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
+ t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
+ + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+ t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
+ t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
+ + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+ if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
+ return -1;
+ if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe, &xscr) < 0) {
+ xdl_free_env(&xe);
+ return -1;
+ }
+ if (!xscr) {
+ /* If this happens, the changes are identical. */
+ xdl_free_env(&xe);
+ m->mode = 4;
+ continue;
+ }
+ x = xscr;
+ m->i1 = xscr->i1 + i1;
+ m->chg1 = xscr->chg1;
+ m->i2 = xscr->i2 + i2;
+ m->chg2 = xscr->chg2;
+ while (xscr->next) {
+ xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t));
+ if (!m2) {
+ xdl_free_env(&xe);
+ xdl_free_script(x);
+ return -1;
+ }
+ xscr = xscr->next;
+ m2->next = m->next;
+ m->next = m2;
+ m = m2;
+ m->mode = 0;
+ m->i1 = xscr->i1 + i1;
+ m->chg1 = xscr->chg1;
+ m->i2 = xscr->i2 + i2;
+ m->chg2 = xscr->chg2;
+ }
+ xdl_free_env(&xe);
+ xdl_free_script(x);
+ }
+ return 0;
+}
+
+static int line_contains_alnum(const char *ptr, long size)
+{
+ while (size--)
+ if (isalnum((unsigned char)*(ptr++)))
+ return 1;
+ return 0;
+}
+
+static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
+{
+ for (; chg; chg--, i++)
+ if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+ xe->xdf2.recs[i]->size))
+ return 1;
+ return 0;
+}
+
+/*
+ * This function merges m and m->next, marking everything between those hunks
+ * as conflicting, too.
+ */
+static void xdl_merge_two_conflicts(xdmerge_t *m)
+{
+ xdmerge_t *next_m = m->next;
+ m->chg1 = next_m->i1 + next_m->chg1 - m->i1;
+ m->chg2 = next_m->i2 + next_m->chg2 - m->i2;
+ m->next = next_m->next;
+ free(next_m);
+}
+
+/*
+ * If there are less than 3 non-conflicting lines between conflicts,
+ * it appears simpler -- because it takes up less (or as many) lines --
+ * if the lines are moved into the conflicts.
+ */
+static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
+ int simplify_if_no_alnum)
+{
+ int result = 0;
+
+ if (!m)
+ return result;
+ for (;;) {
+ xdmerge_t *next_m = m->next;
+ int begin, end;
+
+ if (!next_m)
+ return result;
+
+ begin = m->i1 + m->chg1;
+ end = next_m->i1;
+
+ if (m->mode != 0 || next_m->mode != 0 ||
+ (end - begin > 3 &&
+ (!simplify_if_no_alnum ||
+ lines_contain_alnum(xe1, begin, end - begin)))) {
+ m = next_m;
+ } else {
+ result++;
+ xdl_merge_two_conflicts(m);
+ }
+ }
+}
+
+/*
+ * level == 0: mark all overlapping changes as conflict
+ * level == 1: mark overlapping changes as conflict only if not identical
+ * level == 2: analyze non-identical changes for minimal conflict set
+ * level == 3: analyze non-identical changes for minimal conflict set, but
+ * treat hunks not containing any letter or number as conflicting
+ *
+ * returns < 0 on error, == 0 for no conflicts, else number of conflicts
+ */
+static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
+ xdfenv_t *xe2, xdchange_t *xscr2,
+ xmparam_t const *xmp, mmbuffer_t *result)
+{
+ xdmerge_t *changes, *c;
+ xpparam_t const *xpp = &xmp->xpp;
+ const char *const ancestor_name = xmp->ancestor;
+ const char *const name1 = xmp->file1;
+ const char *const name2 = xmp->file2;
+ int i0, i1, i2, chg0, chg1, chg2;
+ int level = xmp->level;
+ int style = xmp->style;
+ int favor = xmp->favor;
+
+ if (style == XDL_MERGE_DIFF3) {
+ /*
+ * "diff3 -m" output does not make sense for anything
+ * more aggressive than XDL_MERGE_EAGER.
+ */
+ if (XDL_MERGE_EAGER < level)
+ level = XDL_MERGE_EAGER;
+ }
+
+ c = changes = NULL;
+
+ while (xscr1 && xscr2) {
+ if (!changes)
+ changes = c;
+ if (xscr1->i1 + xscr1->chg1 < xscr2->i1) {
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr2->i2 - xscr2->i1 + xscr1->i1;
+ chg0 = xscr1->chg1;
+ chg1 = xscr1->chg2;
+ chg2 = xscr1->chg1;
+ if (xdl_append_merge(&c, 1,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr1 = xscr1->next;
+ continue;
+ }
+ if (xscr2->i1 + xscr2->chg1 < xscr1->i1) {
+ i0 = xscr2->i1;
+ i1 = xscr1->i2 - xscr1->i1 + xscr2->i1;
+ i2 = xscr2->i2;
+ chg0 = xscr2->chg1;
+ chg1 = xscr2->chg1;
+ chg2 = xscr2->chg2;
+ if (xdl_append_merge(&c, 2,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr2 = xscr2->next;
+ continue;
+ }
+ if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 ||
+ xscr1->chg1 != xscr2->chg1 ||
+ xscr1->chg2 != xscr2->chg2 ||
+ xdl_merge_cmp_lines(xe1, xscr1->i2,
+ xe2, xscr2->i2,
+ xscr1->chg2, xpp->flags)) {
+ /* conflict */
+ int off = xscr1->i1 - xscr2->i1;
+ int ffo = off + xscr1->chg1 - xscr2->chg1;
+
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr2->i2;
+ if (off > 0) {
+ i0 -= off;
+ i1 -= off;
+ }
+ else
+ i2 += off;
+ chg0 = xscr1->i1 + xscr1->chg1 - i0;
+ chg1 = xscr1->i2 + xscr1->chg2 - i1;
+ chg2 = xscr2->i2 + xscr2->chg2 - i2;
+ if (ffo < 0) {
+ chg0 -= ffo;
+ chg1 -= ffo;
+ } else
+ chg2 += ffo;
+ if (xdl_append_merge(&c, 0,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ }
+
+ i1 = xscr1->i1 + xscr1->chg1;
+ i2 = xscr2->i1 + xscr2->chg1;
+
+ if (i1 >= i2)
+ xscr2 = xscr2->next;
+ if (i2 >= i1)
+ xscr1 = xscr1->next;
+ }
+ while (xscr1) {
+ if (!changes)
+ changes = c;
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+ chg0 = xscr1->chg1;
+ chg1 = xscr1->chg2;
+ chg2 = xscr1->chg1;
+ if (xdl_append_merge(&c, 1,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr1 = xscr1->next;
+ }
+ while (xscr2) {
+ if (!changes)
+ changes = c;
+ i0 = xscr2->i1;
+ i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+ i2 = xscr2->i2;
+ chg0 = xscr2->chg1;
+ chg1 = xscr2->chg1;
+ chg2 = xscr2->chg2;
+ if (xdl_append_merge(&c, 2,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr2 = xscr2->next;
+ }
+ if (!changes)
+ changes = c;
+ /* refine conflicts */
+ if (XDL_MERGE_ZEALOUS <= level &&
+ (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
+ xdl_simplify_non_conflicts(xe1, changes,
+ XDL_MERGE_ZEALOUS < level) < 0)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ /* output */
+ if (result) {
+ int marker_size = xmp->marker_size;
+ size_t size;
+
+ if (xdl_fill_merge_buffer(&size, xe1, name1, xe2, name2,
+ ancestor_name,
+ favor, changes, NULL, style,
+ marker_size) < 0)
+ return -1;
+
+ result->ptr = xdl_malloc(size);
+ if (!result->ptr) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ result->size = size;
+ if (xdl_fill_merge_buffer(&size, xe1, name1, xe2, name2,
+ ancestor_name, favor, changes,
+ result->ptr, style, marker_size) < 0)
+ return -1;
+ }
+ return xdl_cleanup_merge(changes);
+}
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+ xmparam_t const *xmp, mmbuffer_t *result)
+{
+ xdchange_t *xscr1, *xscr2;
+ xdfenv_t xe1, xe2;
+ int status;
+ xpparam_t const *xpp = &xmp->xpp;
+
+ result->ptr = NULL;
+ result->size = 0;
+
+ if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) {
+ return -1;
+ }
+ if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) {
+ xdl_free_env(&xe1);
+ return -1;
+ }
+ if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe1, &xscr1) < 0) {
+ xdl_free_env(&xe1);
+ return -1;
+ }
+ if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe2, &xscr2) < 0) {
+ xdl_free_script(xscr1);
+ xdl_free_env(&xe1);
+ xdl_free_env(&xe2);
+ return -1;
+ }
+ status = 0;
+ if (!xscr1) {
+ result->ptr = xdl_malloc(mf2->size);
+ memcpy(result->ptr, mf2->ptr, mf2->size);
+ result->size = mf2->size;
+ } else if (!xscr2) {
+ result->ptr = xdl_malloc(mf1->size);
+ memcpy(result->ptr, mf1->ptr, mf1->size);
+ result->size = mf1->size;
+ } else {
+ status = xdl_do_merge(&xe1, xscr1,
+ &xe2, xscr2,
+ xmp, result);
+ }
+ xdl_free_script(xscr1);
+ xdl_free_script(xscr2);
+
+ xdl_free_env(&xe1);
+ xdl_free_env(&xe2);
+
+ return status;
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+/*
+ * The basic idea of patience diff is to find lines that are unique in
+ * both files. These are intuitively the ones that we want to see as
+ * common lines.
+ *
+ * The maximal ordered sequence of such line pairs (where ordered means
+ * that the order in the sequence agrees with the order of the lines in
+ * both files) naturally defines an initial set of common lines.
+ *
+ * Now, the algorithm tries to extend the set of common lines by growing
+ * the line ranges where the files have identical lines.
+ *
+ * Between those common lines, the patience diff algorithm is applied
+ * recursively, until no unique line pairs can be found; these line ranges
+ * are handled by the well-known Myers algorithm.
+ */
+
+#define NON_UNIQUE ULONG_MAX
+
+/*
+ * This is a hash mapping from line hash to line numbers in the first and
+ * second file.
+ */
+struct hashmap {
+ int nr, alloc;
+ struct entry {
+ unsigned long hash;
+ /*
+ * 0 = unused entry, 1 = first line, 2 = second, etc.
+ * line2 is NON_UNIQUE if the line is not unique
+ * in either the first or the second file.
+ */
+ unsigned long line1, line2;
+ /*
+ * "next" & "previous" are used for the longest common
+ * sequence;
+ * initially, "next" reflects only the order in file1.
+ */
+ struct entry *next, *previous;
+ } *entries, *first, *last;
+ /* were common records found? */
+ unsigned long has_matches;
+ mmfile_t *file1, *file2;
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+/* The argument "pass" is 1 for the first file, 2 for the second. */
+static void insert_record(int line, struct hashmap *map, int pass)
+{
+ xrecord_t **records = pass == 1 ?
+ map->env->xdf1.recs : map->env->xdf2.recs;
+ xrecord_t *record = records[line - 1], *other;
+ /*
+ * After xdl_prepare_env() (or more precisely, due to
+ * xdl_classify_record()), the "ha" member of the records (AKA lines)
+ * is _not_ the hash anymore, but a linearized version of it. In
+ * other words, the "ha" member is guaranteed to start with 0 and
+ * the second record's ha can only be 0 or 1, etc.
+ *
+ * So we multiply ha by 2 in the hope that the hashing was
+ * "unique enough".
+ */
+ int index = (int)((record->ha << 1) % map->alloc);
+
+ while (map->entries[index].line1) {
+ other = map->env->xdf1.recs[map->entries[index].line1 - 1];
+ if (map->entries[index].hash != record->ha ||
+ !xdl_recmatch(record->ptr, record->size,
+ other->ptr, other->size,
+ map->xpp->flags)) {
+ if (++index >= map->alloc)
+ index = 0;
+ continue;
+ }
+ if (pass == 2)
+ map->has_matches = 1;
+ if (pass == 1 || map->entries[index].line2)
+ map->entries[index].line2 = NON_UNIQUE;
+ else
+ map->entries[index].line2 = line;
+ return;
+ }
+ if (pass == 2)
+ return;
+ map->entries[index].line1 = line;
+ map->entries[index].hash = record->ha;
+ if (!map->first)
+ map->first = map->entries + index;
+ if (map->last) {
+ map->last->next = map->entries + index;
+ map->entries[index].previous = map->last;
+ }
+ map->last = map->entries + index;
+ map->nr++;
+}
+
+/*
+ * This function has to be called for each recursion into the inter-hunk
+ * parts, as previously non-unique lines can become unique when being
+ * restricted to a smaller part of the files.
+ *
+ * It is assumed that env has been prepared using xdl_prepare().
+ */
+static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ struct hashmap *result,
+ int line1, int count1, int line2, int count2)
+{
+ result->file1 = file1;
+ result->file2 = file2;
+ result->xpp = xpp;
+ result->env = env;
+
+ /* We know exactly how large we want the hash map */
+ result->alloc = count1 * 2;
+ result->entries = (struct entry *)
+ xdl_malloc(result->alloc * sizeof(struct entry));
+ if (!result->entries)
+ return -1;
+ memset(result->entries, 0, result->alloc * sizeof(struct entry));
+
+ /* First, fill with entries from the first file */
+ while (count1--)
+ insert_record(line1++, result, 1);
+
+ /* Then search for matches in the second file */
+ while (count2--)
+ insert_record(line2++, result, 2);
+
+ return 0;
+}
+
+/*
+ * Find the longest sequence with a smaller last element (meaning a smaller
+ * line2, as we construct the sequence with entries ordered by line1).
+ */
+static int binary_search(struct entry **sequence, int longest,
+ struct entry *entry)
+{
+ int left = -1, right = longest;
+
+ while (left + 1 < right) {
+ int middle = (left + right) / 2;
+ /* by construction, no two entries can be equal */
+ if (sequence[middle]->line2 > entry->line2)
+ right = middle;
+ else
+ left = middle;
+ }
+ /* return the index in "sequence", _not_ the sequence length */
+ return left;
+}
+
+/*
+ * The idea is to start with the list of common unique lines sorted by
+ * the order in file1. For each of these pairs, the longest (partial)
+ * sequence whose last element's line2 is smaller is determined.
+ *
+ * For efficiency, the sequences are kept in a list containing exactly one
+ * item per sequence length: the sequence with the smallest last
+ * element (in terms of line2).
+ */
+static struct entry *find_longest_common_sequence(struct hashmap *map)
+{
+ struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+ int longest = 0, i;
+ struct entry *entry;
+
+ for (entry = map->first; entry; entry = entry->next) {
+ if (!entry->line2 || entry->line2 == NON_UNIQUE)
+ continue;
+ i = binary_search(sequence, longest, entry);
+ entry->previous = i < 0 ? NULL : sequence[i];
+ sequence[++i] = entry;
+ if (i == longest)
+ longest++;
+ }
+
+ /* No common unique lines were found */
+ if (!longest) {
+ xdl_free(sequence);
+ return NULL;
+ }
+
+ /* Iterate starting at the last element, adjusting the "next" members */
+ entry = sequence[longest - 1];
+ entry->next = NULL;
+ while (entry->previous) {
+ entry->previous->next = entry;
+ entry = entry->previous;
+ }
+ xdl_free(sequence);
+ return entry;
+}
+
+static int match(struct hashmap *map, int line1, int line2)
+{
+ xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
+ xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
+ return xdl_recmatch(record1->ptr, record1->size,
+ record2->ptr, record2->size, map->xpp->flags);
+}
+
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2);
+
+static int walk_common_sequence(struct hashmap *map, struct entry *first,
+ int line1, int count1, int line2, int count2)
+{
+ int end1 = line1 + count1, end2 = line2 + count2;
+ int next1, next2;
+
+ for (;;) {
+ /* Try to grow the line ranges of common lines */
+ if (first) {
+ next1 = first->line1;
+ next2 = first->line2;
+ while (next1 > line1 && next2 > line2 &&
+ match(map, next1 - 1, next2 - 1)) {
+ next1--;
+ next2--;
+ }
+ } else {
+ next1 = end1;
+ next2 = end2;
+ }
+ while (line1 < next1 && line2 < next2 &&
+ match(map, line1, line2)) {
+ line1++;
+ line2++;
+ }
+
+ /* Recurse */
+ if (next1 > line1 || next2 > line2) {
+ struct hashmap submap;
+
+ memset(&submap, 0, sizeof(submap));
+ if (patience_diff(map->file1, map->file2,
+ map->xpp, map->env,
+ line1, next1 - line1,
+ line2, next2 - line2))
+ return -1;
+ }
+
+ if (!first)
+ return 0;
+
+ while (first->next &&
+ first->next->line1 == first->line1 + 1 &&
+ first->next->line2 == first->line2 + 1)
+ first = first->next;
+
+ line1 = first->line1 + 1;
+ line2 = first->line2 + 1;
+
+ first = first->next;
+ }
+}
+
+static int fall_back_to_classic_diff(struct hashmap *map,
+ int line1, int count1, int line2, int count2)
+{
+ xpparam_t xpp;
+ xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
+
+ return xdl_fall_back_diff(map->env, &xpp,
+ line1, count1, line2, count2);
+}
+
+/*
+ * Recursively find the longest common sequence of unique lines,
+ * and if none was found, ask xdl_do_diff() to do the job.
+ *
+ * This function assumes that env was prepared with xdl_prepare_env().
+ */
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2)
+{
+ struct hashmap map;
+ struct entry *first;
+ int result = 0;
+
+ /* trivial case: one side is empty */
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&map, 0, sizeof(map));
+ if (fill_hashmap(file1, file2, xpp, env, &map,
+ line1, count1, line2, count2))
+ return -1;
+
+ /* are there any matching lines at all? */
+ if (!map.has_matches) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ xdl_free(map.entries);
+ return 0;
+ }
+
+ first = find_longest_common_sequence(&map);
+ if (first)
+ result = walk_common_sequence(&map, first,
+ line1, count1, line2, count2);
+ else
+ result = fall_back_to_classic_diff(&map,
+ line1, count1, line2, count2);
+
+ xdl_free(map.entries);
+ return result;
+}
+
+int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ /* environment is cleaned up in xdl_diff() */
+ return patience_diff(file1, file2, xpp, env,
+ 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+#define XDL_KPDIS_RUN 4
+#define XDL_MAX_EQLIMIT 1024
+#define XDL_SIMSCAN_WINDOW 100
+#define XDL_GUESS_NLINES1 256
+#define XDL_GUESS_NLINES2 20
+
+
+typedef struct s_xdlclass {
+ struct s_xdlclass *next;
+ unsigned long ha;
+ char const *line;
+ long size;
+ long idx;
+ long len1, len2;
+} xdlclass_t;
+
+typedef struct s_xdlclassifier {
+ unsigned int hbits;
+ long hsize;
+ xdlclass_t **rchash;
+ chastore_t ncha;
+ xdlclass_t **rcrecs;
+ long alloc;
+ long count;
+ long flags;
+} xdlclassifier_t;
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
+static void xdl_free_classifier(xdlclassifier_t *cf);
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+ xdlclassifier_t *cf, xdfile_t *xdf);
+static void xdl_free_ctx(xdfile_t *xdf);
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
+ cf->flags = flags;
+
+ cf->hbits = xdl_hashbits((unsigned int) size);
+ cf->hsize = 1 << cf->hbits;
+
+ if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) {
+
+ return -1;
+ }
+ if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) {
+
+ xdl_cha_free(&cf->ncha);
+ return -1;
+ }
+ memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
+
+ cf->alloc = size;
+ if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+ xdl_free(cf->rchash);
+ xdl_cha_free(&cf->ncha);
+ return -1;
+ }
+
+ cf->count = 0;
+
+ return 0;
+}
+
+
+static void xdl_free_classifier(xdlclassifier_t *cf) {
+
+ xdl_free(cf->rcrecs);
+ xdl_free(cf->rchash);
+ xdl_cha_free(&cf->ncha);
+}
+
+
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec) {
+ long hi;
+ char const *line;
+ xdlclass_t *rcrec;
+ xdlclass_t **rcrecs;
+
+ line = rec->ptr;
+ hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
+ for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
+ if (rcrec->ha == rec->ha &&
+ xdl_recmatch(rcrec->line, rcrec->size,
+ rec->ptr, rec->size, cf->flags))
+ break;
+
+ if (!rcrec) {
+ if (!(rcrec = xdl_cha_alloc(&cf->ncha))) {
+
+ return -1;
+ }
+ rcrec->idx = cf->count++;
+ if (cf->count > cf->alloc) {
+ cf->alloc *= 2;
+ if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+ return -1;
+ }
+ cf->rcrecs = rcrecs;
+ }
+ cf->rcrecs[rcrec->idx] = rcrec;
+ rcrec->line = line;
+ rcrec->size = rec->size;
+ rcrec->ha = rec->ha;
+ rcrec->len1 = rcrec->len2 = 0;
+ rcrec->next = cf->rchash[hi];
+ cf->rchash[hi] = rcrec;
+ }
+
+ (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
+ rec->ha = (unsigned long) rcrec->idx;
+
+ hi = (long) XDL_HASHLONG(rec->ha, hbits);
+ rec->next = rhash[hi];
+ rhash[hi] = rec;
+
+ return 0;
+}
+
+
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+ xdlclassifier_t *cf, xdfile_t *xdf) {
+ unsigned int hbits;
+ long nrec, hsize, bsize;
+ unsigned long hav;
+ char const *blk, *cur, *top, *prev;
+ xrecord_t *crec;
+ xrecord_t **recs, **rrecs;
+ xrecord_t **rhash;
+ unsigned long *ha;
+ char *rchg;
+ long *rindex;
+
+ ha = NULL;
+ rindex = NULL;
+ rchg = NULL;
+ rhash = NULL;
+ recs = NULL;
+
+ if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
+ goto abort;
+ if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+ goto abort;
+
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+ hbits = hsize = 0;
+ else {
+ hbits = xdl_hashbits((unsigned int) narec);
+ hsize = 1 << hbits;
+ if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+ goto abort;
+ memset(rhash, 0, hsize * sizeof(xrecord_t *));
+ }
+
+ nrec = 0;
+ if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
+ for (top = blk + bsize; cur < top; ) {
+ prev = cur;
+ hav = xdl_hash_record(&cur, top, xpp->flags);
+ if (nrec >= narec) {
+ narec *= 2;
+ if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
+ goto abort;
+ recs = rrecs;
+ }
+ if (!(crec = xdl_cha_alloc(&xdf->rcha)))
+ goto abort;
+ crec->ptr = prev;
+ crec->size = (long) (cur - prev);
+ crec->ha = hav;
+ recs[nrec++] = crec;
+
+ if ((XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+ xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+ goto abort;
+ }
+ }
+
+ if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+ goto abort;
+ memset(rchg, 0, (nrec + 2) * sizeof(char));
+
+ if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long))))
+ goto abort;
+ if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long))))
+ goto abort;
+
+ xdf->nrec = nrec;
+ xdf->recs = recs;
+ xdf->hbits = hbits;
+ xdf->rhash = rhash;
+ xdf->rchg = rchg + 1;
+ xdf->rindex = rindex;
+ xdf->nreff = 0;
+ xdf->ha = ha;
+ xdf->dstart = 0;
+ xdf->dend = nrec - 1;
+
+ return 0;
+
+abort:
+ xdl_free(ha);
+ xdl_free(rindex);
+ xdl_free(rchg);
+ xdl_free(rhash);
+ xdl_free(recs);
+ xdl_cha_free(&xdf->rcha);
+ return -1;
+}
+
+
+static void xdl_free_ctx(xdfile_t *xdf) {
+
+ xdl_free(xdf->rhash);
+ xdl_free(xdf->rindex);
+ xdl_free(xdf->rchg - 1);
+ xdl_free(xdf->ha);
+ xdl_free(xdf->recs);
+ xdl_cha_free(&xdf->rcha);
+}
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe) {
+ long enl1, enl2, sample;
+ xdlclassifier_t cf;
+
+ memset(&cf, 0, sizeof(cf));
+
+ /*
+ * For histogram diff, we can afford a smaller sample size and
+ * thus a poorer estimate of the number of lines, as the hash
+ * table (rhash) won't be filled up/grown. The number of lines
+ * (nrecs) will be updated correctly anyway by
+ * xdl_prepare_ctx().
+ */
+ sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+ ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+ enl1 = xdl_guess_lines(mf1, sample) + 1;
+ enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+ if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF &&
+ xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+ return -1;
+
+ if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+ if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+
+ if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+ (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+ xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf2);
+ xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+
+ if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)
+ xdl_free_classifier(&cf);
+
+ return 0;
+}
+
+
+void xdl_free_env(xdfenv_t *xe) {
+
+ xdl_free_ctx(&xe->xdf2);
+ xdl_free_ctx(&xe->xdf1);
+}
+
+
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
+ long r, rdis0, rpdis0, rdis1, rpdis1;
+
+ /*
+ * Limits the window the is examined during the similar-lines
+ * scan. The loops below stops when dis[i - r] == 1 (line that
+ * has no match), but there are corner cases where the loop
+ * proceed all the way to the extremities by causing huge
+ * performance penalties in case of big files.
+ */
+ if (i - s > XDL_SIMSCAN_WINDOW)
+ s = i - XDL_SIMSCAN_WINDOW;
+ if (e - i > XDL_SIMSCAN_WINDOW)
+ e = i + XDL_SIMSCAN_WINDOW;
+
+ /*
+ * Scans the lines before 'i' to find a run of lines that either
+ * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1).
+ * Note that we always call this function with dis[i] > 1, so the
+ * current line (i) is already a multimatch line.
+ */
+ for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
+ if (!dis[i - r])
+ rdis0++;
+ else if (dis[i - r] == 2)
+ rpdis0++;
+ else
+ break;
+ }
+ /*
+ * If the run before the line 'i' found only multimatch lines, we
+ * return 0 and hence we don't make the current line (i) discarded.
+ * We want to discard multimatch lines only when they appear in the
+ * middle of runs with nomatch lines (dis[j] == 0).
+ */
+ if (rdis0 == 0)
+ return 0;
+ for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
+ if (!dis[i + r])
+ rdis1++;
+ else if (dis[i + r] == 2)
+ rpdis1++;
+ else
+ break;
+ }
+ /*
+ * If the run after the line 'i' found only multimatch lines, we
+ * return 0 and hence we don't make the current line (i) discarded.
+ */
+ if (rdis1 == 0)
+ return 0;
+ rdis1 += rdis0;
+ rpdis1 += rpdis0;
+
+ return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
+}
+
+
+/*
+ * Try to reduce the problem complexity, discard records that have no
+ * matches on the other file. Also, lines that have multiple matches
+ * might be potentially discarded if they happear in a run of discardable.
+ */
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+ long i, nm, nreff, mlim;
+ xrecord_t **recs;
+ xdlclass_t *rcrec;
+ char *dis, *dis1, *dis2;
+
+ if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
+
+ return -1;
+ }
+ memset(dis, 0, xdf1->nrec + xdf2->nrec + 2);
+ dis1 = dis;
+ dis2 = dis1 + xdf1->nrec + 1;
+
+ if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+ mlim = XDL_MAX_EQLIMIT;
+ for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len2 : 0;
+ dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+ }
+
+ if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+ mlim = XDL_MAX_EQLIMIT;
+ for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len1 : 0;
+ dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+ }
+
+ for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+ i <= xdf1->dend; i++, recs++) {
+ if (dis1[i] == 1 ||
+ (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
+ xdf1->rindex[nreff] = i;
+ xdf1->ha[nreff] = (*recs)->ha;
+ nreff++;
+ } else
+ xdf1->rchg[i] = 1;
+ }
+ xdf1->nreff = nreff;
+
+ for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+ i <= xdf2->dend; i++, recs++) {
+ if (dis2[i] == 1 ||
+ (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
+ xdf2->rindex[nreff] = i;
+ xdf2->ha[nreff] = (*recs)->ha;
+ nreff++;
+ } else
+ xdf2->rchg[i] = 1;
+ }
+ xdf2->nreff = nreff;
+
+ xdl_free(dis);
+
+ return 0;
+}
+
+
+/*
+ * Early trim initial and terminal matching records.
+ */
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
+ long i, lim;
+ xrecord_t **recs1, **recs2;
+
+ recs1 = xdf1->recs;
+ recs2 = xdf2->recs;
+ for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+ i++, recs1++, recs2++)
+ if ((*recs1)->ha != (*recs2)->ha)
+ break;
+
+ xdf1->dstart = xdf2->dstart = i;
+
+ recs1 = xdf1->recs + xdf1->nrec - 1;
+ recs2 = xdf2->recs + xdf2->nrec - 1;
+ for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
+ if ((*recs1)->ha != (*recs2)->ha)
+ break;
+
+ xdf1->dend = xdf1->nrec - i - 1;
+ xdf2->dend = xdf2->nrec - i - 1;
+
+ return 0;
+}
+
+
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+
+ if (xdl_trim_ends(xdf1, xdf2) < 0 ||
+ xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XPREPARE_H)
+#define XPREPARE_H
+
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe);
+void xdl_free_env(xdfenv_t *xe);
+
+
+
+#endif /* #if !defined(XPREPARE_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XTYPES_H)
+#define XTYPES_H
+
+
+
+typedef struct s_chanode {
+ struct s_chanode *next;
+ long icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+ chanode_t *head, *tail;
+ long isize, nsize;
+ chanode_t *ancur;
+ chanode_t *sncur;
+ long scurr;
+} chastore_t;
+
+typedef struct s_xrecord {
+ struct s_xrecord *next;
+ char const *ptr;
+ long size;
+ unsigned long ha;
+} xrecord_t;
+
+typedef struct s_xdfile {
+ chastore_t rcha;
+ long nrec;
+ unsigned int hbits;
+ xrecord_t **rhash;
+ long dstart, dend;
+ xrecord_t **recs;
+ char *rchg;
+ long *rindex;
+ long nreff;
+ unsigned long *ha;
+} xdfile_t;
+
+typedef struct s_xdfenv {
+ xdfile_t xdf1, xdf2;
+} xdfenv_t;
+
+
+
+#endif /* #if !defined(XTYPES_H) */
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+
+
+long xdl_bogosqrt(long n) {
+ long i;
+
+ /*
+ * Classical integer square root approximation using shifts.
+ */
+ for (i = 1; n > 0; n >>= 2)
+ i <<= 1;
+
+ return i;
+}
+
+
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+ xdemitcb_t *ecb) {
+ int i = 2;
+ mmbuffer_t mb[3];
+
+ mb[0].ptr = (char *) pre;
+ mb[0].size = psize;
+ mb[1].ptr = (char *) rec;
+ mb[1].size = size;
+ if (size > 0 && rec[size - 1] != '\n') {
+ mb[2].ptr = (char *) "\n\\ No newline at end of file\n";
+ mb[2].size = strlen(mb[2].ptr);
+ i++;
+ }
+ if (ecb->outf(ecb->priv, mb, i) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size)
+{
+ *size = (long)mmf->size;
+ return mmf->ptr;
+}
+
+
+long xdl_mmfile_size(mmfile_t *mmf)
+{
+ return (long)mmf->size;
+}
+
+
+int xdl_cha_init(chastore_t *cha, long isize, long icount) {
+
+ cha->head = cha->tail = NULL;
+ cha->isize = isize;
+ cha->nsize = icount * isize;
+ cha->ancur = cha->sncur = NULL;
+ cha->scurr = 0;
+
+ return 0;
+}
+
+
+void xdl_cha_free(chastore_t *cha) {
+ chanode_t *cur, *tmp;
+
+ for (cur = cha->head; (tmp = cur) != NULL;) {
+ cur = cur->next;
+ xdl_free(tmp);
+ }
+}
+
+
+void *xdl_cha_alloc(chastore_t *cha) {
+ chanode_t *ancur;
+ void *data;
+
+ if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) {
+ if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) {
+
+ return NULL;
+ }
+ ancur->icurr = 0;
+ ancur->next = NULL;
+ if (cha->tail)
+ cha->tail->next = ancur;
+ if (!cha->head)
+ cha->head = ancur;
+ cha->tail = ancur;
+ cha->ancur = ancur;
+ }
+
+ data = (char *) ancur + sizeof(chanode_t) + ancur->icurr;
+ ancur->icurr += cha->isize;
+
+ return data;
+}
+
+long xdl_guess_lines(mmfile_t *mf, long sample) {
+ long nl = 0, size, tsize = 0;
+ char const *data, *cur, *top;
+
+ if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
+ for (top = data + size; nl < sample && cur < top; ) {
+ nl++;
+ if (!(cur = memchr(cur, '\n', top - cur)))
+ cur = top;
+ else
+ cur++;
+ }
+ tsize += (long) (cur - data);
+ }
+
+ if (nl && tsize)
+ nl = xdl_mmfile_size(mf) / (tsize / nl);
+
+ return nl + 1;
+}
+
+int xdl_blankline(const char *line, long size, long flags)
+{
+ long i;
+
+ if (!(flags & XDF_WHITESPACE_FLAGS))
+ return (size <= 1);
+
+ for (i = 0; i < size && XDL_ISSPACE(line[i]); i++)
+ ;
+
+ return (i == size);
+}
+
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
+{
+ int i1, i2;
+
+ if (s1 == s2 && !memcmp(l1, l2, s1))
+ return 1;
+ if (!(flags & XDF_WHITESPACE_FLAGS))
+ return 0;
+
+ i1 = 0;
+ i2 = 0;
+
+ /*
+ * -w matches everything that matches with -b, and -b in turn
+ * matches everything that matches with --ignore-space-at-eol.
+ *
+ * Each flavor of ignoring needs different logic to skip whitespaces
+ * while we have both sides to compare.
+ */
+ if (flags & XDF_IGNORE_WHITESPACE) {
+ goto skip_ws;
+ while (i1 < s1 && i2 < s2) {
+ if (l1[i1++] != l2[i2++])
+ return 0;
+ skip_ws:
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ }
+ } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+ while (i1 < s1 && i2 < s2) {
+ if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
+ /* Skip matching spaces and try again */
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ continue;
+ }
+ if (l1[i1++] != l2[i2++])
+ return 0;
+ }
+ } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
+ while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++])
+ ; /* keep going */
+ }
+
+ /*
+ * After running out of one side, the remaining side must have
+ * nothing but whitespace for the lines to match. Note that
+ * ignore-whitespace-at-eol case may break out of the loop
+ * while there still are characters remaining on both lines.
+ */
+ if (i1 < s1) {
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ if (s1 != i1)
+ return 0;
+ }
+ if (i2 < s2) {
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ return (s2 == i2);
+ }
+ return 1;
+}
+
+static unsigned long xdl_hash_record_with_whitespace(char const **data,
+ char const *top, long flags) {
+ unsigned long ha = 5381;
+ char const *ptr = *data;
+
+ for (; ptr < top && *ptr != '\n'; ptr++) {
+ if (XDL_ISSPACE(*ptr)) {
+ const char *ptr2 = ptr;
+ int at_eol;
+ while (ptr + 1 < top && XDL_ISSPACE(ptr[1])
+ && ptr[1] != '\n')
+ ptr++;
+ at_eol = (top <= ptr + 1 || ptr[1] == '\n');
+ if (flags & XDF_IGNORE_WHITESPACE)
+ ; /* already handled */
+ else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+ && !at_eol) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) ' ';
+ }
+ else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
+ && !at_eol) {
+ while (ptr2 != ptr + 1) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr2;
+ ptr2++;
+ }
+ }
+ continue;
+ }
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr;
+ }
+ *data = ptr < top ? ptr + 1: ptr;
+
+ return ha;
+}
+
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+ unsigned long ha = 5381;
+ char const *ptr = *data;
+
+ if (flags & XDF_WHITESPACE_FLAGS)
+ return xdl_hash_record_with_whitespace(data, top, flags);
+
+ for (; ptr < top && *ptr != '\n'; ptr++) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr;
+ }
+ *data = ptr < top ? ptr + 1: ptr;
+
+ return ha;
+}
+
+
+unsigned int xdl_hashbits(unsigned int size) {
+ unsigned int val = 1, bits = 0;
+
+ for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++);
+ return bits ? bits: 1;
+}
+
+
+int xdl_num_out(char *out, long val) {
+ char *ptr, *str = out;
+ char buf[32];
+
+ ptr = buf + sizeof(buf) - 1;
+ *ptr = '\0';
+ if (val < 0) {
+ *--ptr = '-';
+ val = -val;
+ }
+ for (; val && ptr > buf; val /= 10)
+ *--ptr = "0123456789"[val % 10];
+ if (*ptr)
+ for (; *ptr; ptr++, str++)
+ *str = *ptr;
+ else
+ *str++ = '0';
+ *str = '\0';
+
+ return (int)(str - out);
+}
+
+
+long xdl_atol(char const *str, char const **next) {
+ long val, base;
+ char const *top;
+
+ for (top = str; XDL_ISDIGIT(*top); top++);
+ if (next)
+ *next = top;
+ for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
+ val += base * (long)(*top - '0');
+ return val;
+}
+
+
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+ const char *func, long funclen, xdemitcb_t *ecb) {
+ int nb = 0;
+ mmbuffer_t mb;
+ char buf[128];
+
+ memcpy(buf, "@@ -", 4);
+ nb += 4;
+
+ nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1);
+
+ if (c1 != 1) {
+ memcpy(buf + nb, ",", 1);
+ nb += 1;
+
+ nb += xdl_num_out(buf + nb, c1);
+ }
+
+ memcpy(buf + nb, " +", 2);
+ nb += 2;
+
+ nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1);
+
+ if (c2 != 1) {
+ memcpy(buf + nb, ",", 1);
+ nb += 1;
+
+ nb += xdl_num_out(buf + nb, c2);
+ }
+
+ memcpy(buf + nb, " @@", 3);
+ nb += 3;
+ if (func && funclen) {
+ buf[nb++] = ' ';
+ if (funclen > (long)sizeof(buf) - nb - 1)
+ funclen = (long)sizeof(buf) - nb - 1;
+ memcpy(buf + nb, func, funclen);
+ nb += funclen;
+ }
+ buf[nb++] = '\n';
+
+ mb.ptr = buf;
+ mb.size = nb;
+ if (ecb->outf(ecb->priv, &mb, 1) < 0)
+ return -1;
+
+ return 0;
+}
+
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2)
+{
+ /*
+ * This probably does not work outside Git, since
+ * we have a very simple mmfile structure.
+ *
+ * Note: ideally, we would reuse the prepared environment, but
+ * the libxdiff interface does not (yet) allow for diffing only
+ * ranges of lines instead of the whole files.
+ */
+ mmfile_t subfile1, subfile2;
+ xdfenv_t env;
+
+ subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
+ subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
+ diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+ subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
+ subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
+ diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+ if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
+ return -1;
+
+ memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+ memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+ xdl_free_env(&env);
+
+ return 0;
+}
--- /dev/null
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XUTILS_H)
+#define XUTILS_H
+
+
+
+long xdl_bogosqrt(long n);
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+ xdemitcb_t *ecb);
+int xdl_cha_init(chastore_t *cha, long isize, long icount);
+void xdl_cha_free(chastore_t *cha);
+void *xdl_cha_alloc(chastore_t *cha);
+void *xdl_cha_first(chastore_t *cha);
+void *xdl_cha_next(chastore_t *cha);
+long xdl_guess_lines(mmfile_t *mf, long sample);
+int xdl_blankline(const char *line, long size, long flags);
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
+unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned int xdl_hashbits(unsigned int size);
+int xdl_num_out(char *out, long val);
+long xdl_atol(char const *str, char const **next);
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+ const char *func, long funclen, xdemitcb_t *ecb);
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2);
+
+
+
+#endif /* #if !defined(XUTILS_H) */
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <zlib.h>
+
+#include "zstream.h"
+#include "buffer.h"
+
+#define ZSTREAM_BUFFER_SIZE (1024 * 1024)
+#define ZSTREAM_BUFFER_MIN_EXTRA 8
+
+static int zstream_seterr(git_zstream *zs)
+{
+ if (zs->zerr == Z_OK || zs->zerr == Z_STREAM_END)
+ return 0;
+
+ if (zs->zerr == Z_MEM_ERROR)
+ giterr_set_oom();
+ else if (zs->z.msg)
+ giterr_set_str(GITERR_ZLIB, zs->z.msg);
+ else
+ giterr_set(GITERR_ZLIB, "Unknown compression error");
+
+ return -1;
+}
+
+int git_zstream_init(git_zstream *zstream, git_zstream_t type)
+{
+ zstream->type = type;
+
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflateInit(&zstream->z);
+ else
+ zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
+ return zstream_seterr(zstream);
+}
+
+void git_zstream_free(git_zstream *zstream)
+{
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateEnd(&zstream->z);
+ else
+ deflateEnd(&zstream->z);
+}
+
+void git_zstream_reset(git_zstream *zstream)
+{
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateReset(&zstream->z);
+ else
+ deflateReset(&zstream->z);
+ zstream->in = NULL;
+ zstream->in_len = 0;
+ zstream->zerr = Z_STREAM_END;
+}
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len)
+{
+ zstream->in = in;
+ zstream->in_len = in_len;
+ zstream->zerr = Z_OK;
+ return 0;
+}
+
+bool git_zstream_done(git_zstream *zstream)
+{
+ return (!zstream->in_len && zstream->zerr == Z_STREAM_END);
+}
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream)
+{
+ if (zstream->in_len > ZSTREAM_BUFFER_SIZE)
+ return ZSTREAM_BUFFER_SIZE;
+ else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA)
+ return zstream->in_len;
+ else
+ return ZSTREAM_BUFFER_MIN_EXTRA;
+}
+
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
+{
+ int zflush = Z_FINISH;
+ size_t out_remain = *out_len;
+
+ if (zstream->in_len && zstream->zerr == Z_STREAM_END) {
+ giterr_set(GITERR_ZLIB, "zlib input had trailing garbage");
+ return -1;
+ }
+
+ while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
+ size_t out_queued, in_queued, out_used, in_used;
+
+ /* set up in data */
+ zstream->z.next_in = (Bytef *)zstream->in;
+ zstream->z.avail_in = (uInt)zstream->in_len;
+ if ((size_t)zstream->z.avail_in != zstream->in_len) {
+ zstream->z.avail_in = INT_MAX;
+ zflush = Z_NO_FLUSH;
+ } else {
+ zflush = Z_FINISH;
+ }
+ in_queued = (size_t)zstream->z.avail_in;
+
+ /* set up out data */
+ zstream->z.next_out = out;
+ zstream->z.avail_out = (uInt)out_remain;
+ if ((size_t)zstream->z.avail_out != out_remain)
+ zstream->z.avail_out = INT_MAX;
+ out_queued = (size_t)zstream->z.avail_out;
+
+ /* compress next chunk */
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflate(&zstream->z, zflush);
+ else
+ zstream->zerr = deflate(&zstream->z, zflush);
+
+ if (zstream->zerr == Z_STREAM_ERROR)
+ return zstream_seterr(zstream);
+
+ out_used = (out_queued - zstream->z.avail_out);
+ out_remain -= out_used;
+ out = ((char *)out) + out_used;
+
+ in_used = (in_queued - zstream->z.avail_in);
+ zstream->in_len -= in_used;
+ zstream->in += in_used;
+ }
+
+ /* either we finished the input or we did not flush the data */
+ assert(zstream->in_len > 0 || zflush == Z_FINISH);
+
+ /* set out_size to number of bytes actually written to output */
+ *out_len = *out_len - out_remain;
+
+ return 0;
+}
+
+static int zstream_buf(git_buf *out, const void *in, size_t in_len, git_zstream_t type)
+{
+ git_zstream zs = GIT_ZSTREAM_INIT;
+ int error = 0;
+
+ if ((error = git_zstream_init(&zs, type)) < 0)
+ return error;
+
+ if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
+ goto done;
+
+ while (!git_zstream_done(&zs)) {
+ size_t step = git_zstream_suggest_output_len(&zs), written;
+
+ if ((error = git_buf_grow_by(out, step)) < 0)
+ goto done;
+
+ written = out->asize - out->size;
+
+ if ((error = git_zstream_get_output(
+ out->ptr + out->size, &written, &zs)) < 0)
+ goto done;
+
+ out->size += written;
+ }
+
+ /* NULL terminate for consistency if possible */
+ if (out->size < out->asize)
+ out->ptr[out->size] = '\0';
+
+done:
+ git_zstream_free(&zs);
+ return error;
+}
+
+int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE);
+}
+
+int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE);
+}
--- /dev/null
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_zstream_h__
+#define INCLUDE_zstream_h__
+
+#include <zlib.h>
+
+#include "common.h"
+#include "buffer.h"
+
+typedef enum {
+ GIT_ZSTREAM_INFLATE,
+ GIT_ZSTREAM_DEFLATE,
+} git_zstream_t;
+
+typedef struct {
+ z_stream z;
+ git_zstream_t type;
+ const char *in;
+ size_t in_len;
+ int zerr;
+} git_zstream;
+
+#define GIT_ZSTREAM_INIT {{0}}
+
+int git_zstream_init(git_zstream *zstream, git_zstream_t type);
+void git_zstream_free(git_zstream *zstream);
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len);
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream);
+
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream);
+
+bool git_zstream_done(git_zstream *zstream);
+
+void git_zstream_reset(git_zstream *zstream);
+
+int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len);
+int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len);
+
+#endif /* INCLUDE_zstream_h__ */
clean-cargo-deps.patch
local-jquery.patch
-use-system-libgit2.patch
+# TODO: re-enable after the freeze
+#use-system-libgit2.patch
disable-net-tests.patch
--build=$(DEB_BUILD_RUST_TYPE) \
--host=$(DEB_HOST_RUST_TYPE) \
--target=$(DEB_TARGET_RUST_TYPE)
+# TODO: rm after the freeze
+ cp -a debian/libgit2 vendor/libgit2-sys-0.6.6/
override_dh_auto_build-arch:
RUST_BACKTRACE=1 $(MAKE)
$(CURDIR)/config.stamp \
$(CURDIR)/Makefile \
$(CURDIR)/cargo-stage0
+# TODO: rm after the freeze
+ -$(RM) -rf vendor/libgit2-sys-0.6.6/libgit2
CROSS_SBUILD = DEB_BUILD_OPTIONS=nocheck sbuild --profiles=nocheck \
--build-failed-commands '%SBUILD_SHELL' \